mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 07:20:24 +08:00
feat: 新增统一文件模块,统一文件操作
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.8.5",
|
||||
"element-plus": "^2.8.6",
|
||||
"js-base64": "^3.7.7",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -57,7 +57,7 @@
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.79.5",
|
||||
"sass": "^1.80.3",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.9",
|
||||
"vue-eslint-parser": "^9.4.3"
|
||||
|
||||
@@ -14,4 +14,5 @@ export default {
|
||||
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
||||
getLdapEnabled: () => request.get('/auth/ldap/enabled'),
|
||||
ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
|
||||
getFileDetail: (keys: string[]) => request.get(`/sys/files/detail/${keys.join(',')}`),
|
||||
};
|
||||
|
||||
@@ -209,6 +209,36 @@ export function joinClientParams(): string {
|
||||
return `token=${getToken()}&clientId=${getClientId()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件url地址
|
||||
* @param key 文件key
|
||||
* @returns 文件url
|
||||
*/
|
||||
export function getFileUrl(key: string) {
|
||||
return `${baseUrl}/sys/files/${key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统文件上传url
|
||||
* @param key 文件key
|
||||
* @returns 文件上传url
|
||||
*/
|
||||
export function getUploadFileUrl(key: string = '') {
|
||||
return `${baseUrl}/sys/files/upload?token=${getToken()}&fileKey=${key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
* @param key 文件key
|
||||
*/
|
||||
export function downloadFile(key: string) {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('href', `${getFileUrl(key)}`);
|
||||
a.setAttribute('target', '_blank');
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
function parseResult(result: Result) {
|
||||
if (result.code === ResultEnum.SUCCESS) {
|
||||
return result.data;
|
||||
|
||||
60
mayfly_go_web/src/components/file/FileInfo.vue
Normal file
60
mayfly_go_web/src/components/file/FileInfo.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-tooltip :content="formatByteSize(fileDetail.size)" placement="left">
|
||||
<el-link v-if="props.canDownload" target="_blank" rel="noopener noreferrer" icon="Download" type="primary" :href="getFileUrl(props.fileKey)"></el-link>
|
||||
</el-tooltip>
|
||||
|
||||
{{ fileDetail.filename }}
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import openApi from '@/common/openApi';
|
||||
import { getFileUrl } from '@/common/request';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
const props = defineProps({
|
||||
fileKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
files: {
|
||||
type: [Array],
|
||||
},
|
||||
canDownload: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
setFileInfo();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.fileKey,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
setFileInfo();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const fileDetail: any = ref({});
|
||||
|
||||
const setFileInfo = async () => {
|
||||
if (!props.fileKey) {
|
||||
return;
|
||||
}
|
||||
if (props.files && props.files.length > 0) {
|
||||
const file: any = props.files.find((file: any) => {
|
||||
return file.fileKey === props.fileKey;
|
||||
});
|
||||
fileDetail.value = file;
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await openApi.getFileDetail([props.fileKey]);
|
||||
fileDetail.value = files?.[0];
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
@@ -1 +1 @@
|
||||
@import 'common/transition.scss';
|
||||
@use 'common/transition.scss';
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'mixins/index.scss';
|
||||
@use 'mixins/index' as mixins;
|
||||
|
||||
/* Button 按钮
|
||||
------------------------------- */
|
||||
@@ -97,7 +97,7 @@
|
||||
.el-sub-menu .iconfont,
|
||||
.el-menu-item .fa,
|
||||
.el-sub-menu .fa {
|
||||
@include generalIcon;
|
||||
@include mixins.generalIcon;
|
||||
}
|
||||
|
||||
// 水平菜单、横向菜单高亮 背景色,鼠标 hover 时,有子级菜单的背景色
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import './app.scss';
|
||||
@import './base.scss';
|
||||
@import './other.scss';
|
||||
@import './element.scss';
|
||||
@import './media/media.scss';
|
||||
@import './waves.scss';
|
||||
@import './dark.scss';
|
||||
@import './iconSelector.scss';
|
||||
@use './app.scss';
|
||||
@use './base.scss';
|
||||
@use './other.scss';
|
||||
@use './element.scss';
|
||||
@use './media/media.scss';
|
||||
@use './waves.scss';
|
||||
@use './dark.scss';
|
||||
@use './iconSelector.scss';
|
||||
@@ -1,94 +1,109 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
.big-data-down-left {
|
||||
width: 100% !important;
|
||||
flex-direction: unset !important;
|
||||
flex-wrap: wrap;
|
||||
.flex-warp-item {
|
||||
min-height: 196.24px;
|
||||
padding: 0 7.5px 15px 15px !important;
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.big-data-down-center {
|
||||
width: 100% !important;
|
||||
.big-data-down-center-one,
|
||||
.big-data-down-center-two {
|
||||
min-height: 196.24px;
|
||||
padding-left: 15px !important;
|
||||
.big-data-down-center-one-content {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
.flex-warp-item-box {
|
||||
@extend .big-data-down-center-one-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
.big-data-down-right {
|
||||
.flex-warp-item {
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
padding-left: 15px !important;
|
||||
}
|
||||
&:last-of-type {
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.big-data-down-left {
|
||||
width: 100% !important;
|
||||
flex-direction: unset !important;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.flex-warp-item {
|
||||
min-height: 196.24px;
|
||||
padding: 0 7.5px 15px 15px !important;
|
||||
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.big-data-down-center {
|
||||
width: 100% !important;
|
||||
|
||||
.big-data-down-center-one,
|
||||
.big-data-down-center-two {
|
||||
min-height: 196.24px;
|
||||
padding-left: 15px !important;
|
||||
|
||||
.big-data-down-center-one-content {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
|
||||
.flex-warp-item-box {
|
||||
@extend .big-data-down-center-one-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.big-data-down-right {
|
||||
.flex-warp-item {
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
}
|
||||
|
||||
&:nth-of-type(2) {
|
||||
padding-left: 15px !important;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
.flex-warp-item-box {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度大于768px小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (min-width: $sm) and (max-width: $lg) {
|
||||
.chart-warp-bottom {
|
||||
.big-data-down-left {
|
||||
width: 50% !important;
|
||||
}
|
||||
.big-data-down-center {
|
||||
width: 50% !important;
|
||||
}
|
||||
.big-data-down-right {
|
||||
.flex-warp-item {
|
||||
width: 50% !important;
|
||||
&:nth-of-type(2) {
|
||||
padding-left: 7.5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: index.$sm) and (max-width: index.$lg) {
|
||||
.chart-warp-bottom {
|
||||
.big-data-down-left {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.big-data-down-center {
|
||||
width: 50% !important;
|
||||
}
|
||||
|
||||
.big-data-down-right {
|
||||
.flex-warp-item {
|
||||
width: 50% !important;
|
||||
|
||||
&:nth-of-type(2) {
|
||||
padding-left: 7.5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度小于1200px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $lg) {
|
||||
.chart-warp-top {
|
||||
.up-left {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.chart-warp-bottom {
|
||||
overflow-y: auto !important;
|
||||
flex-wrap: wrap;
|
||||
.big-data-down-right {
|
||||
width: 100% !important;
|
||||
flex-direction: unset !important;
|
||||
flex-wrap: wrap;
|
||||
.flex-warp-item {
|
||||
min-height: 196.24px;
|
||||
padding: 0 7.5px 15px 15px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$lg) {
|
||||
.chart-warp-top {
|
||||
.up-left {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-warp-bottom {
|
||||
overflow-y: auto !important;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.big-data-down-right {
|
||||
width: 100% !important;
|
||||
flex-direction: unset !important;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.flex-warp-item {
|
||||
min-height: 196.24px;
|
||||
padding: 0 7.5px 15px 15px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
.el-cascader__dropdown.el-popper {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.el-cascader__dropdown.el-popper {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss';
|
||||
|
||||
/* 页面宽度小于800px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: 800px) {
|
||||
.el-dialog {
|
||||
width: 90% !important;
|
||||
}
|
||||
.el-dialog.is-fullscreen {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
.el-dialog {
|
||||
width: 90% !important;
|
||||
}
|
||||
|
||||
.el-dialog.is-fullscreen {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,38 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
.error {
|
||||
.error-flex {
|
||||
flex-direction: column-reverse !important;
|
||||
height: auto !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.right,
|
||||
.left {
|
||||
flex: unset !important;
|
||||
display: flex !important;
|
||||
}
|
||||
.left-item {
|
||||
margin: auto !important;
|
||||
}
|
||||
.right img {
|
||||
max-width: 450px !important;
|
||||
@extend .left-item;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.error {
|
||||
.error-flex {
|
||||
flex-direction: column-reverse !important;
|
||||
height: auto !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.right,
|
||||
.left {
|
||||
flex: unset !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.left-item {
|
||||
margin: auto !important;
|
||||
}
|
||||
|
||||
.right img {
|
||||
max-width: 450px !important;
|
||||
@extend .left-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度大于768px小于992px
|
||||
------------------------------- */
|
||||
@media screen and (min-width: $sm) and (max-width: $md) {
|
||||
.error {
|
||||
.error-flex {
|
||||
padding-left: 30px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: index.$sm) and (max-width: index.$md) {
|
||||
.error {
|
||||
.error-flex {
|
||||
padding-left: 30px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
.el-form-item__label {
|
||||
width: 100% !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
.el-form-item__content {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.el-form-item__label {
|
||||
width: 100% !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
.home-warning-media,
|
||||
.home-dynamic-media {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
|
||||
.home-warning-media,
|
||||
.home-dynamic-media {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,61 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
// MessageBox 弹框
|
||||
.el-message-box {
|
||||
width: 80% !important;
|
||||
}
|
||||
@media screen and (max-width: index.$xs) {
|
||||
|
||||
// MessageBox 弹框
|
||||
.el-message-box {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
// Breadcrumb 面包屑
|
||||
.layout-navbars-breadcrumb-hide {
|
||||
display: none;
|
||||
}
|
||||
// 外链视图
|
||||
.layout-view-link {
|
||||
a {
|
||||
max-width: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
// 菜单搜索
|
||||
.layout-search-dialog {
|
||||
.el-autocomplete {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
|
||||
// Breadcrumb 面包屑
|
||||
.layout-navbars-breadcrumb-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 外链视图
|
||||
.layout-view-link {
|
||||
a {
|
||||
max-width: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单搜索
|
||||
.layout-search-dialog {
|
||||
.el-autocomplete {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度小于1000px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: 1000px) {
|
||||
// 布局配置
|
||||
.layout-drawer-content-flex {
|
||||
position: relative;
|
||||
&::after {
|
||||
content: '手机版不支持切换布局';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
height: 140px;
|
||||
line-height: 140px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 布局配置
|
||||
.layout-drawer-content-flex {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '手机版不支持切换布局';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
height: 140px;
|
||||
line-height: 140px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
.login-container {
|
||||
.login-content {
|
||||
width: 90% !important;
|
||||
padding: 20px 0 !important;
|
||||
}
|
||||
.login-content-form-btn {
|
||||
width: 100% !important;
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
.login-copyright {
|
||||
.login-copyright-msg {
|
||||
white-space: unset !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$xs) {
|
||||
.login-container {
|
||||
.login-content {
|
||||
width: 90% !important;
|
||||
padding: 20px 0 !important;
|
||||
}
|
||||
|
||||
.login-content-form-btn {
|
||||
width: 100% !important;
|
||||
padding: 12px 0 !important;
|
||||
}
|
||||
|
||||
.login-copyright {
|
||||
.login-copyright-msg {
|
||||
white-space: unset !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
@import './login.scss';
|
||||
@import './error.scss';
|
||||
@import './layout.scss';
|
||||
@import './personal.scss';
|
||||
@import './tagsView.scss';
|
||||
@import './home.scss';
|
||||
@import './chart.scss';
|
||||
@import './form.scss';
|
||||
@import './scrollbar.scss';
|
||||
@import './pagination.scss';
|
||||
@import './dialog.scss';
|
||||
@import './cityLinkage.scss';
|
||||
@use './login.scss';
|
||||
@use './error.scss';
|
||||
@use './layout.scss';
|
||||
@use './personal.scss';
|
||||
@use './tagsView.scss';
|
||||
@use './home.scss';
|
||||
@use './chart.scss';
|
||||
@use './form.scss';
|
||||
@use './scrollbar.scss';
|
||||
@use './pagination.scss';
|
||||
@use './dialog.scss';
|
||||
@use './cityLinkage.scss';
|
||||
@@ -1,15 +1,16 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于576px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $xs) {
|
||||
.el-pager,
|
||||
.el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
@media screen and (max-width: index.$xs) {
|
||||
|
||||
.el-pager,
|
||||
.el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认居中对齐
|
||||
.el-pagination {
|
||||
text-align: center !important;
|
||||
}
|
||||
text-align: center !important;
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
.personal-info {
|
||||
padding-left: 0 !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.personal-recommend-col {
|
||||
margin-bottom: 15px;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.personal-info {
|
||||
padding-left: 0 !important;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.personal-recommend-col {
|
||||
margin-bottom: 15px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,66 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
// 滚动条的宽度
|
||||
::-webkit-scrollbar {
|
||||
width: 3px !important;
|
||||
height: 3px !important;
|
||||
}
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: var(--bg-main-color);
|
||||
}
|
||||
// 滚动条的设置
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
border-radius: 5px;
|
||||
transition: 0.3s background-color;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
// element plus scrollbar
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
width: 2px !important;
|
||||
}
|
||||
.el-scrollbar__bar.is-horizontal {
|
||||
height: 2px !important;
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
|
||||
// 滚动条的宽度
|
||||
::-webkit-scrollbar {
|
||||
width: 3px !important;
|
||||
height: 3px !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: var(--bg-main-color);
|
||||
}
|
||||
|
||||
// 滚动条的设置
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
border-radius: 5px;
|
||||
transition: 0.3s background-color;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
|
||||
// element plus scrollbar
|
||||
.el-scrollbar__bar.is-vertical {
|
||||
width: 2px !important;
|
||||
}
|
||||
|
||||
.el-scrollbar__bar.is-horizontal {
|
||||
height: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页面宽度大于768px
|
||||
------------------------------- */
|
||||
@media screen and (min-width: 769px) {
|
||||
// 滚动条的宽度
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: var(--bg-main-color);
|
||||
}
|
||||
// 滚动条的设置
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
border-radius: 5px;
|
||||
transition: 0.3s background-color;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
|
||||
// 滚动条的宽度
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: var(--bg-main-color);
|
||||
}
|
||||
|
||||
// 滚动条的设置
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
border-radius: 5px;
|
||||
transition: 0.3s background-color;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
@import './index.scss';
|
||||
@use './index.scss' as index;
|
||||
|
||||
/* 页面宽度小于768px
|
||||
------------------------------- */
|
||||
@media screen and (max-width: $sm) {
|
||||
.tags-view-form {
|
||||
.tags-view-form-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: index.$sm) {
|
||||
.tags-view-form {
|
||||
.tags-view-form-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
/* 第三方图标字体间距/大小设置
|
||||
------------------------------- */
|
||||
@mixin generalIcon {
|
||||
font-size: 14px !important;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
font-size: 14px !important;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 文本不换行
|
||||
------------------------------- */
|
||||
@mixin text-no-wrap() {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 多行文本溢出
|
||||
------------------------------- */
|
||||
@mixin text-ellipsis($line: 2) {
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $line;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: $line;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* 滚动条(页面未使用) div 中使用:
|
||||
@@ -35,22 +35,26 @@
|
||||
// @include scrollBar;
|
||||
// }
|
||||
@mixin scrollBar {
|
||||
// 滚动条凹槽的颜色,还可以设置边框属性
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
// 滚动条的宽度
|
||||
&::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
}
|
||||
// 滚动条的设置
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #dddddd;
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
// 滚动条凹槽的颜色,还可以设置边框属性
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
// 滚动条的宽度
|
||||
&::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
}
|
||||
|
||||
// 滚动条的设置
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #dddddd;
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #bbb;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,31 @@
|
||||
/* wangeditor富文本编辑器
|
||||
------------------------------- */
|
||||
.w-e-toolbar {
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
z-index: 2 !important;
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-bottom: 1px solid #ebeef5 !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
.w-e-text-container {
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-top: none !important;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
z-index: 1 !important;
|
||||
border: 1px solid #ebeef5 !important;
|
||||
border-top: none !important;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
/* web端自定义截屏
|
||||
------------------------------- */
|
||||
#screenShotContainer {
|
||||
z-index: 9998 !important;
|
||||
z-index: 9998 !important;
|
||||
}
|
||||
|
||||
#toolPanel {
|
||||
height: 42px !important;
|
||||
height: 42px !important;
|
||||
}
|
||||
|
||||
#optionPanel {
|
||||
height: 37px !important;
|
||||
}
|
||||
height: 37px !important;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -144,6 +144,7 @@ import { personApi } from '@/views/personal/api';
|
||||
import { AccountUsernamePattern } from '@/common/pattern';
|
||||
import { getToken } from '@/common/utils/storage';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getFileUrl } from '@/common/request';
|
||||
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
@@ -347,26 +348,34 @@ const updateUserInfo = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loginResDeal = (loginRes: any) => {
|
||||
const loginResDeal = async (loginRes: any) => {
|
||||
state.loginRes = loginRes;
|
||||
// 用户信息
|
||||
const userInfos = {
|
||||
name: loginRes.name,
|
||||
username: loginRes.username,
|
||||
// 头像
|
||||
photo: letterAvatar(loginRes.username),
|
||||
time: new Date().getTime(),
|
||||
lastLoginTime: loginRes.lastLoginTime,
|
||||
lastLoginIp: loginRes.lastLoginIp,
|
||||
photo: '',
|
||||
};
|
||||
|
||||
const avatarFileKey = `avatar_${loginRes.username}`;
|
||||
const avatarFileDetail = await openApi.getFileDetail([avatarFileKey]);
|
||||
// 说明存在头像文件
|
||||
if (avatarFileDetail.length > 0) {
|
||||
userInfos.photo = getFileUrl(avatarFileKey);
|
||||
} else {
|
||||
userInfos.photo = letterAvatar(loginRes.username);
|
||||
}
|
||||
|
||||
// 存储用户信息到浏览器缓存
|
||||
saveUser(userInfos);
|
||||
// 1、请注意执行顺序(存储用户信息到vuex)
|
||||
useUserInfo().setUserInfo(userInfos);
|
||||
|
||||
const token = loginRes.token;
|
||||
// 如果不需要 otp校验,则该token即为accessToken,否则为otp校验token
|
||||
// 如果不需要otp校验,则该token即为accessToken,否则为otp校验token
|
||||
if (loginRes.otp == -1) {
|
||||
signInSuccess(token, loginRes.refresh_token);
|
||||
return;
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item :command="{ type: 'dumpDb', data }"> 导出 </el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
<!-- <el-dropdown-item
|
||||
:command="{ type: 'backupDb', data }"
|
||||
v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
|
||||
>
|
||||
@@ -98,7 +98,7 @@
|
||||
v-if="actionBtns[perms.restoreDb] && supportAction('restoreDb', data.type)"
|
||||
>
|
||||
恢复任务
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="db-transfer-file">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" width="900px">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" width="1000px">
|
||||
<page-table
|
||||
ref="pageTableRef"
|
||||
:data="state.tableData"
|
||||
@@ -25,6 +25,10 @@
|
||||
<el-button v-auth="perms.del" :disabled="state.selectionData.length < 1" @click="del()" type="danger" icon="delete">删除</el-button>
|
||||
</template>
|
||||
|
||||
<template #fileKey="{ data }">
|
||||
<FileInfo :fileKey="data.fileKey" :canDownload="actionBtns[perms.down] && data.status === 2" />
|
||||
</template>
|
||||
|
||||
<template #fileDbType="{ data }">
|
||||
<span>
|
||||
<SvgIcon :name="getDbDialect(data.fileDbType).getInfo().icon" :size="18" />
|
||||
@@ -32,37 +36,16 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #status="{ data }">
|
||||
<span>
|
||||
<el-tag v-if="data.status == 1" class="ml-2" type="primary">执行中</el-tag>
|
||||
<el-tag v-else-if="data.status == 2" class="ml-2" type="success">成功</el-tag>
|
||||
<el-tag v-else-if="data.status == -1" class="ml-2" type="danger">失败</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
<template #action="{ data }">
|
||||
<el-button v-if="actionBtns[perms.run] && data.status === 2" @click="openRun(data)" type="primary" link>执行</el-button>
|
||||
<el-button v-if="actionBtns[perms.rename] && data.status === 2" @click="rename(data)" type="warning" link>重命名</el-button>
|
||||
<el-button v-if="actionBtns[perms.down] && data.status === 2" @click="down(data)" type="primary" link>下载</el-button>
|
||||
<el-button v-if="actionBtns[perms.run] && data.status === DbTransferFileStatusEnum.Success.value" @click="openRun(data)" type="primary" link
|
||||
>执行</el-button
|
||||
>
|
||||
<el-button v-if="data.logId" @click="openLog(data)" type="success" link>日志</el-button>
|
||||
</template>
|
||||
</page-table>
|
||||
<TerminalLog v-model:log-id="state.logsDialog.logId" v-model:visible="state.logsDialog.visible" :title="state.logsDialog.title" />
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="state.renameDialog.title" v-model="state.renameDialog.visible" :destroy-on-close="true" width="400px">
|
||||
<el-form :model="state.renameDialog.renameForm" ref="renameFormRef" label-width="auto">
|
||||
<el-form-item label="文件名" prop="fileName" required>
|
||||
<el-input v-model="state.renameDialog.renameForm.fileName" placeholder="请输入文件名" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="state.renameDialog.cancel()">取 消</el-button>
|
||||
<el-button type="primary" :loading="state.renameDialog.loading" @click="state.renameDialog.btnOk">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog :title="state.runDialog.title" v-model="state.runDialog.visible" :destroy-on-close="true" width="600px">
|
||||
<el-form :model="state.runDialog.runForm" ref="runFormRef" label-width="auto" :rules="state.runDialog.formRules">
|
||||
<el-form-item label="文件数据库类型" prop="dbType">
|
||||
@@ -100,10 +83,10 @@ import { TableColumn } from '@/components/pagetable';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { hasPerms } from '@/components/auth/auth';
|
||||
import TerminalLog from '@/components/terminal/TerminalLog.vue';
|
||||
import config from '@/common/config';
|
||||
import { joinClientParams } from '@/common/request';
|
||||
import DbSelectTree from '@/views/ops/db/component/DbSelectTree.vue';
|
||||
import { getClientId } from '@/common/utils/storage';
|
||||
import FileInfo from '@/components/file/FileInfo.vue';
|
||||
import { DbTransferFileStatusEnum } from './enums';
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: [Object],
|
||||
@@ -116,22 +99,21 @@ const props = defineProps({
|
||||
const dialogVisible = defineModel<boolean>('visible', { default: false });
|
||||
|
||||
const columns = ref([
|
||||
TableColumn.new('fileName', '文件名').setMinWidth(150),
|
||||
TableColumn.new('createTime', '创建时间').setMinWidth(180).isTime(),
|
||||
TableColumn.new('fileKey', '文件').setMinWidth(280).isSlot(),
|
||||
TableColumn.new('createTime', '执行时间').setMinWidth(180).isTime(),
|
||||
TableColumn.new('fileDbType', 'sql语言').setMinWidth(90).isSlot(),
|
||||
TableColumn.new('status', '状态').isSlot(),
|
||||
TableColumn.new('status', '状态').typeTag(DbTransferFileStatusEnum),
|
||||
]);
|
||||
|
||||
const perms = {
|
||||
del: 'db:transfer:files:del',
|
||||
down: 'db:transfer:files:down',
|
||||
rename: 'db:transfer:files:rename',
|
||||
run: 'db:transfer:files:run',
|
||||
};
|
||||
|
||||
const actionBtns = hasPerms([perms.del, perms.down, perms.rename, perms.run]);
|
||||
const actionBtns = hasPerms([perms.del, perms.down, perms.run]);
|
||||
|
||||
const actionWidth = ((actionBtns[perms.rename] ? 1 : 0) + (actionBtns[perms.down] ? 1 : 0) + (actionBtns[perms.run] ? 1 : 0) + 1) * 55;
|
||||
const actionWidth = ((actionBtns[perms.run] ? 1 : 0) + 1) * 55;
|
||||
|
||||
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(actionWidth).fixedRight().alignCenter();
|
||||
|
||||
@@ -141,7 +123,6 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
const renameFormRef: any = ref(null);
|
||||
const runFormRef: any = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
@@ -210,31 +191,6 @@ const state = reactive({
|
||||
}
|
||||
},
|
||||
},
|
||||
renameDialog: {
|
||||
visible: false,
|
||||
title: '文件重命名',
|
||||
renameForm: {
|
||||
id: 0,
|
||||
fileName: '',
|
||||
},
|
||||
loading: false,
|
||||
cancel: function () {
|
||||
state.renameDialog.visible = false;
|
||||
state.renameDialog.renameForm = { id: 0, fileName: '' };
|
||||
},
|
||||
btnOk: function () {
|
||||
renameFormRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) {
|
||||
ElMessage.error('请正确填写信息');
|
||||
return false;
|
||||
}
|
||||
await dbApi.dbTransferFileRename.request(state.renameDialog.renameForm);
|
||||
ElMessage.success('保存成功');
|
||||
state.renameDialog.cancel();
|
||||
await search();
|
||||
});
|
||||
},
|
||||
},
|
||||
selectionData: [], // 选中的数据
|
||||
tableData: [],
|
||||
});
|
||||
@@ -262,18 +218,6 @@ const del = async function () {
|
||||
}
|
||||
};
|
||||
|
||||
const down = function (data: any) {
|
||||
const a = document.createElement('a');
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('href', `${config.baseApiUrl}/dbTransfer/files/down/${data.fileUuid}?${joinClientParams()}`);
|
||||
a.click();
|
||||
a.remove();
|
||||
};
|
||||
const rename = function (data: any) {
|
||||
state.renameDialog.visible = true;
|
||||
const { id, fileName } = data;
|
||||
state.renameDialog.renameForm = { id, fileName };
|
||||
};
|
||||
const openLog = function (data: any) {
|
||||
state.logsDialog.logId = data.logId;
|
||||
state.logsDialog.visible = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Api from '@/common/Api';
|
||||
import {AesEncrypt} from '@/common/crypto';
|
||||
import { AesEncrypt } from '@/common/crypto';
|
||||
|
||||
export const dbApi = {
|
||||
// 获取权限列表
|
||||
@@ -80,7 +80,6 @@ export const dbApi = {
|
||||
dbTransferTaskLogs: Api.newGet('/dbTransfer/{taskId}/logs'),
|
||||
dbTransferFileList: Api.newGet('/dbTransfer/files/{taskId}'),
|
||||
dbTransferFileDel: Api.newPost('/dbTransfer/files/del/{fileId}'),
|
||||
dbTransferFileRename: Api.newPost('/dbTransfer/files/rename'),
|
||||
dbTransferFileRun: Api.newPost('/dbTransfer/files/run'),
|
||||
dbTransferFileDown: Api.newGet('/dbTransfer/files/down/{fileUuid}'),
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ const props = defineProps({
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql']);
|
||||
|
||||
let dbDialect = computed(() => getDbDialect(props.dbType!, props.version));
|
||||
let dbDialect: any = computed(() => getDbDialect(props.dbType!, props.version));
|
||||
|
||||
type ColName = {
|
||||
prop: string;
|
||||
|
||||
@@ -43,3 +43,9 @@ export const DbTransferRunningStateEnum = {
|
||||
Fail: EnumValue.of(-1, '失败').setTagType('danger'),
|
||||
Stop: EnumValue.of(-2, '手动终止').setTagType('warning'),
|
||||
};
|
||||
|
||||
export const DbTransferFileStatusEnum = {
|
||||
Running: EnumValue.of(1, '执行中').setTagType('primary'),
|
||||
Success: EnumValue.of(2, '成功').setTagType('success'),
|
||||
Fail: EnumValue.of(-1, '失败').setTagType('danger'),
|
||||
};
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
@open="getTermOps()"
|
||||
>
|
||||
<page-table ref="pageTableRef" :page-api="machineApi.termOpRecs" :lazy="true" height="100%" v-model:query-form="query" :columns="columns">
|
||||
<template #fileKey="{ data }">
|
||||
<FileInfo :fileKey="data.fileKey" />
|
||||
</template>
|
||||
|
||||
<template #action="{ data }">
|
||||
<el-button @click="playRec(data)" loading-icon="loading" :loading="data.playRecLoding" type="primary" link>回放</el-button>
|
||||
<el-button @click="showExecCmds(data)" type="primary" link>命令</el-button>
|
||||
@@ -49,6 +53,8 @@ import 'asciinema-player/dist/bundle/asciinema-player.css';
|
||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||
import { TableColumn } from '@/components/pagetable';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { getFileUrl } from '@/common/request';
|
||||
import FileInfo from '@/components/file/FileInfo.vue';
|
||||
|
||||
const props = defineProps({
|
||||
visible: { type: Boolean },
|
||||
@@ -62,7 +68,7 @@ const columns = [
|
||||
TableColumn.new('creator', '操作者').setMinWidth(120),
|
||||
TableColumn.new('createTime', '开始时间').isTime().setMinWidth(150),
|
||||
TableColumn.new('endTime', '结束时间').isTime().setMinWidth(150),
|
||||
TableColumn.new('recordFilePath', '文件路径').setMinWidth(200),
|
||||
TableColumn.new('fileKey', '文件').isSlot(),
|
||||
TableColumn.new('action', '操作').isSlot().setMinWidth(120).fixedRight().alignCenter(),
|
||||
];
|
||||
|
||||
@@ -109,14 +115,9 @@ const playRec = async (rec: any) => {
|
||||
player.dispose();
|
||||
}
|
||||
rec.playRecLoding = true;
|
||||
const content = await machineApi.termOpRec.request({
|
||||
recId: rec.id,
|
||||
id: rec.machineId,
|
||||
});
|
||||
|
||||
state.playerDialogVisible = true;
|
||||
nextTick(() => {
|
||||
player = AsciinemaPlayer.create(`data:text/plain;base64,${content}`, playerRef.value, {
|
||||
player = AsciinemaPlayer.create(getFileUrl(rec.fileKey), playerRef.value, {
|
||||
autoPlay: true,
|
||||
speed: 1.0,
|
||||
idleTimeLimit: 2,
|
||||
|
||||
@@ -47,8 +47,6 @@ export const machineApi = {
|
||||
delConf: Api.newDelete('/machines/{machineId}/files/{id}'),
|
||||
// 机器终端操作记录列表
|
||||
termOpRecs: Api.newGet('/machines/{machineId}/term-recs'),
|
||||
// 机器终端操作记录详情
|
||||
termOpRec: Api.newGet('/machines/{id}/term-recs/{recId}'),
|
||||
};
|
||||
|
||||
export const cronJobApi = {
|
||||
|
||||
@@ -118,7 +118,7 @@ const unbindOAuth2 = async () => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../theme/mixins/index.scss';
|
||||
@use '@/theme/mixins/index.scss' as mixins;
|
||||
.personal {
|
||||
.personal-edit {
|
||||
.personal-edit-title {
|
||||
@@ -159,7 +159,7 @@ const unbindOAuth2 = async () => {
|
||||
|
||||
.personal-edit-safe-item-left-value {
|
||||
color: gray;
|
||||
@include text-ellipsis(1);
|
||||
@include mixins.text-ellipsis(1);
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ const viteConfig: UserConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
api: 'modern-compiler', // or 'modern'
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/cryptox"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -196,9 +196,6 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
|
||||
|
||||
executedStatements++
|
||||
_, err = dbConn.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -258,7 +255,7 @@ func (d *Db) DumpSql(rc *req.Ctx) {
|
||||
Tables: tables,
|
||||
DumpDDL: needStruct,
|
||||
DumpData: needData,
|
||||
Writer: writer.NewGzipWriter(rc.GetWriter()),
|
||||
Writer: writerx.NewGzipWriter(rc.GetWriter()),
|
||||
}))
|
||||
|
||||
rc.ReqParam = collx.Kvs("db", dbConn.Info, "database", dbName, "tables", tablesStr, "dumpType", dumpType)
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayfly-go/internal/db/api/form"
|
||||
"mayfly-go/internal/db/api/vo"
|
||||
"mayfly-go/internal/db/application"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/dbm/sqlparser"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
msgapp "mayfly-go/internal/msg/application"
|
||||
msgdto "mayfly-go/internal/msg/application/dto"
|
||||
tagapp "mayfly-go/internal/tag/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"mayfly-go/pkg/utils/anyx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type DbTransferTask struct {
|
||||
@@ -34,6 +33,7 @@ type DbTransferTask struct {
|
||||
TagApp tagapp.TagTree `inject:"TagTreeApp"`
|
||||
MsgApp msgapp.Msg `inject:""`
|
||||
DbSqlExecApp application.DbSqlExec `inject:""`
|
||||
FileApp fileapp.File `inject:""`
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
@@ -41,11 +41,13 @@ func (d *DbTransferTask) Tasks(rc *req.Ctx) {
|
||||
res, err := d.DbTransferTask.GetPageList(queryCond, page, new([]vo.DbTransferTaskListVO))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.DbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
if res.List != nil {
|
||||
list := res.List.(*[]vo.DbTransferTaskListVO)
|
||||
for _, item := range *list {
|
||||
item.RunningState = entity.DbTransferTaskRunStateSuccess
|
||||
if d.DbTransferTask.IsRunning(item.Id) {
|
||||
item.RunningState = entity.DbTransferTaskRunStateRunning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +108,6 @@ func (d *DbTransferTask) Files(rc *req.Ctx) {
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRename(rc *req.Ctx) {
|
||||
fm := &form.DbTransferFileForm{}
|
||||
tFile := req.BindJsonAndCopyTo[*entity.DbTransferFile](rc, fm, new(entity.DbTransferFile))
|
||||
_ = d.DbTransferFile.UpdateById(rc.MetaCtx, tFile)
|
||||
rc.ReqParam = fm
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
fileId := rc.PathParam("fileId")
|
||||
rc.ReqParam = fileId // 记录操作日志
|
||||
@@ -120,50 +115,11 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) {
|
||||
|
||||
uIds := make([]uint64, len(ids))
|
||||
for _, v := range ids {
|
||||
value, err := strconv.Atoi(v)
|
||||
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
|
||||
uIds = append(uIds, uint64(value))
|
||||
uIds = append(uIds, cast.ToUint64(v))
|
||||
}
|
||||
biz.ErrIsNil(d.DbTransferFile.Delete(rc.MetaCtx, uIds...))
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileDown(rc *req.Ctx) {
|
||||
fileUuid := rc.PathParam("fileUuid")
|
||||
if fileUuid == "" {
|
||||
panic(errorx.NewBiz("文件id不能为空"))
|
||||
}
|
||||
|
||||
tFile := &entity.DbTransferFile{FileUuid: fileUuid}
|
||||
|
||||
err := d.DbTransferFile.GetByCond(model.NewModelCond(tFile).Dest(tFile))
|
||||
biz.ErrIsNilAppendErr(err, "查询文件出错 %s")
|
||||
|
||||
// 拼接文件地址,并把文件流输出到客户端
|
||||
brc := config.GetDbBackupRestore()
|
||||
filePath := filepath.Join(fmt.Sprintf("%s/%d/%s.sql", brc.TransferPath, tFile.TaskId, fileUuid))
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败:%s")
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// Get the file information to set the correct response headers
|
||||
fileInfo, err := file.Stat()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败:%s")
|
||||
|
||||
rc.ReqParam = tFile // 记录操作日志
|
||||
// 如果文件名不以 .sql 结尾,则加上 .sql
|
||||
if !strings.HasSuffix(tFile.FileName, ".sql") {
|
||||
tFile.FileName += ".sql"
|
||||
}
|
||||
|
||||
rc.Header("Content-Type", "application/octet-stream")
|
||||
rc.Header("Content-Disposition", "attachment; filename="+tFile.FileName)
|
||||
rc.Header("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
|
||||
_, err = io.Copy(rc.GetWriter(), file)
|
||||
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
|
||||
fm := req.BindJsonAndValid(rc, &form.DbTransferFileRunForm{})
|
||||
@@ -183,7 +139,7 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
if len(errInfo) > 300 {
|
||||
errInfo = errInfo[:300] + "..."
|
||||
}
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", tFile.FileName, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
|
||||
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", tFile.FileKey, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -194,13 +150,7 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
|
||||
}
|
||||
|
||||
func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFileRunForm, tFile *entity.DbTransferFile, targetDbConn *dbi.DbConn) {
|
||||
|
||||
filePath := d.DbTransferFile.GetFilePath(tFile)
|
||||
_, err := os.Stat(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "sql文件不存在:%s")
|
||||
|
||||
file, err := os.Open(filePath)
|
||||
biz.ErrIsNilAppendErr(err, "sql文件读取出错:%s")
|
||||
filename, reader, err := d.FileApp.GetReader(context.TODO(), tFile.FileKey)
|
||||
|
||||
executedStatements := 0
|
||||
progressId := stringx.Rand(32)
|
||||
@@ -213,12 +163,12 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
biz.ErrIsNilAppendErr(err, "连接目标数据库失败: %s")
|
||||
}
|
||||
|
||||
err = sqlparser.SQLSplit(file, func(sql string) error {
|
||||
err = sqlparser.SQLSplit(reader, func(sql string) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ws.SendJsonMsg(ws.UserId(laId), fm.ClientId, msgdto.InfoSqlProgressMsg("sql脚本执行进度", &progressMsg{
|
||||
Id: progressId,
|
||||
Title: tFile.FileName,
|
||||
Title: filename,
|
||||
ExecutedStatements: executedStatements,
|
||||
Terminated: false,
|
||||
}).WithCategory(progressCategory))
|
||||
@@ -233,5 +183,5 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
|
||||
biz.ErrIsNilAppendErr(err, "执行sql失败: %s")
|
||||
}
|
||||
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成:%s", tFile.FileName)).WithClientId(fm.ClientId))
|
||||
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成:%s", filename)).WithClientId(fm.ClientId))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ type DbTransferFileListVO struct {
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
Status int8 `json:"status"`
|
||||
FileDbType string `json:"fileDbType"`
|
||||
FileName string `json:"fileName"`
|
||||
FileUuid string `json:"fileUuid"`
|
||||
FileKey string `json:"fileKey"`
|
||||
LogId uint64 `json:"logId"` // 日志ID
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -231,7 +232,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
log = reqParam.Log
|
||||
}
|
||||
|
||||
writer := reqParam.Writer
|
||||
writer := writerx.NewStringWriter(reqParam.Writer)
|
||||
defer writer.Close()
|
||||
dbId := reqParam.DbId
|
||||
dbName := reqParam.DbName
|
||||
@@ -241,6 +242,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writer.WriteString("\n-- ----------------------------")
|
||||
writer.WriteString("\n-- 导出平台: mayfly-go")
|
||||
writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
|
||||
@@ -306,7 +308,6 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
|
||||
log(fmt.Sprintf("获取表[%s]信息...", tableName))
|
||||
quoteTableName := targetMeta.QuoteIdentifier(tableName)
|
||||
|
||||
writer.TryFlush()
|
||||
// 查询表信息,主要是为了查询表注释
|
||||
tbs, err := srcMeta.GetTables(tableName)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"mayfly-go/internal/db/application/dto"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
sysentity "mayfly-go/internal/sys/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
@@ -20,12 +20,13 @@ import (
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
"os"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -57,9 +58,10 @@ type DbTransferTask interface {
|
||||
type dbTransferAppImpl struct {
|
||||
base.AppImpl[*entity.DbTransferTask, repository.DbTransferTask]
|
||||
|
||||
dbApp Db `inject:"DbApp"`
|
||||
logApp sysapp.Syslog `inject:"SyslogApp"`
|
||||
fileApp DbTransferFile `inject:"DbTransferFileApp"`
|
||||
dbApp Db `inject:"DbApp"`
|
||||
logApp sysapp.Syslog `inject:"SyslogApp"`
|
||||
transferFileApp DbTransferFile `inject:"DbTransferFileApp"`
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) InjectDbTransferTaskRepo(repo repository.DbTransferTask) {
|
||||
@@ -133,7 +135,7 @@ func (app *dbTransferAppImpl) InitCronJob() {
|
||||
}
|
||||
}
|
||||
// 把所有运行中的文件状态设置为失败
|
||||
_ = app.fileApp.UpdateByCond(context.TODO(), &entity.DbTransferFile{Status: entity.DbTransferFileStatusFail}, &entity.DbTransferFile{Status: entity.DbTransferFileStatusRunning})
|
||||
_ = app.transferFileApp.UpdateByCond(context.TODO(), &entity.DbTransferFile{Status: entity.DbTransferFileStatusFail}, &entity.DbTransferFile{Status: entity.DbTransferFileStatusRunning})
|
||||
|
||||
// 把所有需要定时执行的任务添加到定时任务中
|
||||
pageParam := &model.PageParam{
|
||||
@@ -255,7 +257,6 @@ func (app *dbTransferAppImpl) transfer2Db(ctx context.Context, taskId uint64, lo
|
||||
}
|
||||
|
||||
func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64, logId uint64, task *entity.DbTransferTask, srcConn *dbi.DbConn, start time.Time, tables []dbi.Table) {
|
||||
|
||||
// 1、新增迁移文件数据
|
||||
nowTime := time.Now()
|
||||
tFile := &entity.DbTransferFile{
|
||||
@@ -263,14 +264,16 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
CreateTime: &nowTime,
|
||||
Status: entity.DbTransferFileStatusRunning,
|
||||
FileDbType: cmp.Or(task.TargetFileDbType, task.TargetDbType),
|
||||
FileName: fmt.Sprintf("%s.sql", task.TaskName), // 用于下载和展示
|
||||
FileUuid: uuid.New().String(), // 用于存放到磁盘
|
||||
LogId: logId,
|
||||
}
|
||||
_ = app.fileApp.Save(ctx, tFile)
|
||||
_ = app.transferFileApp.Save(ctx, tFile)
|
||||
|
||||
// 新建一个文件,文件位置为 {transferPath}/{taskId}/{uuid}.sql
|
||||
filePath := app.fileApp.GetFilePath(tFile)
|
||||
filename := fmt.Sprintf("dtf_%s_%s.sql", task.TaskName, timex.TimeNo())
|
||||
fileKey, writer, saveFileFunc, err := app.fileApp.NewWriter(ctx, "", filename)
|
||||
if err != nil {
|
||||
app.EndTransfer(ctx, logId, taskId, "创建文件失败", err, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// 从tables提取表名
|
||||
tableNames := make([]string, 0)
|
||||
@@ -278,11 +281,12 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
tableNames = append(tableNames, table.TableName)
|
||||
}
|
||||
// 2、把源库数据迁移到文件
|
||||
app.Log(ctx, logId, fmt.Sprintf("开始迁移表数据到文件: %s", filePath))
|
||||
app.Log(ctx, logId, fmt.Sprintf("开始迁移表数据到文件: %s", filename))
|
||||
|
||||
app.Log(ctx, logId, fmt.Sprintf("目标库文件语言类型: %s", task.TargetFileDbType))
|
||||
|
||||
go func() {
|
||||
defer saveFileFunc()
|
||||
defer app.MarkStop(taskId)
|
||||
defer app.logApp.Flush(logId, true)
|
||||
ctx = context.Background()
|
||||
@@ -294,7 +298,7 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
Tables: tableNames,
|
||||
DumpDDL: true,
|
||||
DumpData: true,
|
||||
Writer: writer.NewFileWriter(filePath),
|
||||
Writer: writer,
|
||||
Log: func(msg string) { // 记录日志
|
||||
app.Log(ctx, logId, msg)
|
||||
},
|
||||
@@ -302,15 +306,16 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
|
||||
if err != nil {
|
||||
app.EndTransfer(ctx, logId, taskId, "数据库迁移失败", err, nil)
|
||||
tFile.Status = entity.DbTransferFileStatusFail
|
||||
_ = app.fileApp.UpdateById(ctx, tFile)
|
||||
_ = app.transferFileApp.UpdateById(ctx, tFile)
|
||||
// 删除文件
|
||||
_ = os.Remove(filePath)
|
||||
_ = app.fileApp.Remove(ctx, fileKey)
|
||||
return
|
||||
}
|
||||
app.EndTransfer(ctx, logId, taskId, "数据库迁移完成", err, nil)
|
||||
|
||||
tFile.Status = entity.DbTransferFileStatusSuccess
|
||||
_ = app.fileApp.UpdateById(ctx, tFile)
|
||||
tFile.FileKey = fileKey
|
||||
_ = app.transferFileApp.UpdateById(ctx, tFile)
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
@@ -2,15 +2,11 @@ package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"mayfly-go/internal/db/config"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
"mayfly-go/internal/db/domain/repository"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/model"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type DbTransferFile interface {
|
||||
@@ -22,14 +18,14 @@ type DbTransferFile interface {
|
||||
Save(ctx context.Context, instanceEntity *entity.DbTransferFile) error
|
||||
|
||||
Delete(ctx context.Context, id ...uint64) error
|
||||
|
||||
GetFilePath(ent *entity.DbTransferFile) string
|
||||
}
|
||||
|
||||
var _ DbTransferFile = (*dbTransferFileAppImpl)(nil)
|
||||
|
||||
type dbTransferFileAppImpl struct {
|
||||
base.AppImpl[*entity.DbTransferFile, repository.DbTransferFile]
|
||||
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) InjectDbTransferFileRepo(repo repository.DbTransferFile) {
|
||||
@@ -51,28 +47,16 @@ func (app *dbTransferFileAppImpl) Save(ctx context.Context, taskEntity *entity.D
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) Delete(ctx context.Context, id ...uint64) error {
|
||||
|
||||
arr, err := app.GetByIds(id, "task_id", "file_uuid")
|
||||
arr, err := app.GetByIds(id, "task_id", "file_key")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除对应的文件
|
||||
for _, file := range arr {
|
||||
_ = os.Remove(app.GetFilePath(file))
|
||||
_ = app.fileApp.Remove(ctx, file.FileKey)
|
||||
}
|
||||
|
||||
// 删除数据
|
||||
return app.DeleteById(ctx, id...)
|
||||
}
|
||||
|
||||
func (app *dbTransferFileAppImpl) GetFilePath(ent *entity.DbTransferFile) string {
|
||||
brc := config.GetDbBackupRestore()
|
||||
if ent.FileUuid == "" {
|
||||
ent.FileUuid = uuid.New().String()
|
||||
}
|
||||
|
||||
filePath := filepath.Join(fmt.Sprintf("%s/%d/%s.sql", brc.TransferPath, ent.TaskId, ent.FileUuid))
|
||||
|
||||
return filePath
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"mayfly-go/internal/db/dbm/dbi"
|
||||
"mayfly-go/internal/db/domain/entity"
|
||||
tagentity "mayfly-go/internal/tag/domain/entity"
|
||||
"mayfly-go/pkg/utils/writer"
|
||||
)
|
||||
|
||||
type SaveDbInstance struct {
|
||||
@@ -22,7 +22,7 @@ type DumpDb struct {
|
||||
|
||||
LogId uint64
|
||||
|
||||
Writer writer.CustomWriter
|
||||
Writer io.WriteCloser
|
||||
Log func(msg string)
|
||||
TargetDbType dbi.DbType
|
||||
}
|
||||
|
||||
@@ -71,15 +71,19 @@ func parseSQL(r io.Reader, callback SQLCallback) error {
|
||||
buffer.Next(size)
|
||||
case inString:
|
||||
if escapeNextChar {
|
||||
// 当前字符是转义后的字符,直接写入。如后一个为" 避免进入r==stringDelimiter判断被当做字符串结束符中断
|
||||
currentStatement.WriteRune(r)
|
||||
escapeNextChar = false
|
||||
} else if r == '\\' {
|
||||
// 当前字符是转义符,设置标志位并写入
|
||||
escapeNextChar = true
|
||||
currentStatement.WriteRune(r)
|
||||
} else if r == stringDelimiter {
|
||||
// 当前字符是字符串结束符,结束字符串处理
|
||||
inString = false
|
||||
currentStatement.WriteRune(r)
|
||||
} else {
|
||||
// 其他字符,直接写入
|
||||
currentStatement.WriteRune(r)
|
||||
}
|
||||
buffer.Next(size)
|
||||
|
||||
@@ -13,8 +13,7 @@ type DbTransferFile struct {
|
||||
TaskId uint64 `orm:"column(task_id)" json:"taskId"` // 迁移任务ID
|
||||
LogId uint64 `orm:"column(log_id)" json:"logId"` // 日志ID
|
||||
FileDbType string `orm:"column(file_db_type)" json:"fileDbType"` // sql文件数据库类型
|
||||
FileName string `orm:"column(file_name)" json:"fileName"` // 显式文件名
|
||||
FileUuid string `orm:"column(file_uuid)" json:"fileUuid"` // 文件真实id,拼接后可以下载
|
||||
FileKey string `orm:"column(file_key)" json:"fileKey"` // 文件
|
||||
}
|
||||
|
||||
func (d *DbTransferFile) TableName() string {
|
||||
|
||||
@@ -37,15 +37,10 @@ func InitDbTransferRouter(router *gin.RouterGroup) {
|
||||
// 导出文件管理-列表
|
||||
req.NewGet("/files/:taskId", d.Files),
|
||||
|
||||
req.NewPost("/files/rename", d.FileRename).Log(req.NewLogSave("dts-删除迁移文件")).RequiredPermissionCode("db:transfer:files:rename"),
|
||||
|
||||
// 导出文件管理-删除
|
||||
req.NewPost("/files/del/:fileId", d.FileDel).Log(req.NewLogSave("dts-删除迁移文件")).RequiredPermissionCode("db:transfer:files:del"),
|
||||
|
||||
req.NewPost("/files/run", d.FileRun).Log(req.NewLogSave("dts-执行sql文件")).RequiredPermissionCode("db:transfer:files:run"),
|
||||
|
||||
// 导出文件管理-下载
|
||||
req.NewGet("/files/down/:fileUuid", d.FileDown).Log(req.NewLogSave("dts-下载迁移文件")).RequiredPermissionCode("db:transfer:files:down"),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(instances, reqs[:])
|
||||
|
||||
53
server/internal/file/api/file.go
Normal file
53
server/internal/file/api/file.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/api/vo"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/model"
|
||||
"mayfly-go/pkg/req"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
FileApp application.File `inject:""`
|
||||
}
|
||||
|
||||
func (f *File) GetFileByKeys(rc *req.Ctx) {
|
||||
keysStr := rc.PathParam("keys")
|
||||
biz.NotEmpty(keysStr, "keys不能为空")
|
||||
|
||||
var files []vo.SimpleFile
|
||||
err := f.FileApp.ListByCondToAny(model.NewCond().In("file_key", strings.Split(keysStr, ",")), &files)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = files
|
||||
}
|
||||
|
||||
func (f *File) GetFileContent(rc *req.Ctx) {
|
||||
key := rc.PathParam("key")
|
||||
biz.NotEmpty(key, "key不能为空")
|
||||
|
||||
filename, reader, err := f.FileApp.GetReader(rc.MetaCtx, key)
|
||||
if err != nil {
|
||||
rc.GetWriter().Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
rc.Download(reader, filename)
|
||||
}
|
||||
|
||||
func (f *File) Upload(rc *req.Ctx) {
|
||||
multipart, err := rc.GetRequest().MultipartReader()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||
file, err := multipart.NextPart()
|
||||
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
|
||||
defer file.Close()
|
||||
|
||||
fileKey, err := f.FileApp.Upload(rc.MetaCtx, rc.Query("fileKey"), file.FileName(), file)
|
||||
biz.ErrIsNil(err)
|
||||
rc.ResData = fileKey
|
||||
}
|
||||
|
||||
func (f *File) Remove(rc *req.Ctx) {
|
||||
biz.ErrIsNil(f.FileApp.Remove(rc.MetaCtx, rc.PathParam("key")))
|
||||
}
|
||||
7
server/internal/file/api/vo/file.go
Normal file
7
server/internal/file/api/vo/file.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package vo
|
||||
|
||||
type SimpleFile struct {
|
||||
Filename string `json:"filename"`
|
||||
FileKey string `json:"fileKey"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
9
server/internal/file/application/application.go
Normal file
9
server/internal/file/application/application.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(new(fileAppImpl), ioc.WithComponentName("FileApp"))
|
||||
}
|
||||
169
server/internal/file/application/file.go
Normal file
169
server/internal/file/application/file.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"mayfly-go/internal/file/config"
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
"mayfly-go/pkg/errorx"
|
||||
"mayfly-go/pkg/logx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"mayfly-go/pkg/utils/writerx"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
type File interface {
|
||||
base.App[*entity.File]
|
||||
|
||||
// Upload 上传文件
|
||||
//
|
||||
// @param fileKey 文件key,若存在则使用存在的文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error)
|
||||
|
||||
// NewWriter 创建文件writer
|
||||
//
|
||||
// @param canEmptyFileKey 文件key,若不为空则使用该文件key,否则生成新的文件key。
|
||||
//
|
||||
// @param filename 文件名,带文件后缀
|
||||
//
|
||||
// @return fileKey 文件key
|
||||
//
|
||||
// @return writer 文件writer
|
||||
//
|
||||
// @return saveFunc 保存文件信息的回调函数 (必须要defer中调用才会入库保存该文件信息)
|
||||
NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error)
|
||||
|
||||
// GetReader 获取文件reader
|
||||
//
|
||||
// @return filename 文件名
|
||||
//
|
||||
// @return reader 文件reader
|
||||
//
|
||||
// @return err 错误
|
||||
GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error)
|
||||
|
||||
// Remove 删除文件
|
||||
Remove(ctx context.Context, fileKey string) error
|
||||
}
|
||||
|
||||
type fileAppImpl struct {
|
||||
base.AppImpl[*entity.File, repository.File]
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) InjectFileRepo(repo repository.File) {
|
||||
f.Repo = repo
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Upload(ctx context.Context, fileKey string, filename string, r io.Reader) (string, error) {
|
||||
fileKey, writer, saveFileFunc, err := f.NewWriter(ctx, fileKey, filename)
|
||||
if err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
defer saveFileFunc()
|
||||
|
||||
if _, err := io.Copy(writer, r); err != nil {
|
||||
return fileKey, err
|
||||
}
|
||||
return fileKey, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) NewWriter(ctx context.Context, canEmptyFileKey string, filename string) (fileKey string, writer *writerx.CountingWriteCloser, saveFunc func() error, err error) {
|
||||
isNewFile := true
|
||||
file := &entity.File{}
|
||||
|
||||
if canEmptyFileKey == "" {
|
||||
canEmptyFileKey = stringx.RandUUID()
|
||||
file.FileKey = canEmptyFileKey
|
||||
} else {
|
||||
file.FileKey = canEmptyFileKey
|
||||
if err := f.GetByCond(file); err == nil {
|
||||
isNewFile = false
|
||||
}
|
||||
}
|
||||
file.Filename = filename
|
||||
|
||||
if !isNewFile {
|
||||
// 先删除旧文件
|
||||
f.remove(ctx, file)
|
||||
}
|
||||
|
||||
// 生产新的文件名
|
||||
newFilename := canEmptyFileKey + filepath.Ext(filename)
|
||||
filepath, w, err := f.newWriter(newFilename)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
file.Path = filepath
|
||||
|
||||
fileKey = canEmptyFileKey
|
||||
writer = writerx.NewCountingWriteCloser(w)
|
||||
// 创建回调函数
|
||||
saveFunc = func() error {
|
||||
// 获取已写入的字节数
|
||||
file.Size = writer.BytesWritten()
|
||||
writer.Close()
|
||||
// 保存文件信息
|
||||
return f.Save(ctx, file)
|
||||
}
|
||||
|
||||
return fileKey, writer, saveFunc, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error) {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return "", nil, errorx.NewBiz("文件不存在")
|
||||
}
|
||||
r, err := os.Open(filepath.Join(config.GetFileConfig().BasePath, file.Path))
|
||||
return file.Filename, r, err
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) Remove(ctx context.Context, fileKey string) error {
|
||||
file := &entity.File{FileKey: fileKey}
|
||||
if err := f.GetByCond(file); err != nil {
|
||||
return errorx.NewBiz("文件不存在")
|
||||
}
|
||||
f.DeleteById(ctx, file.Id)
|
||||
return f.remove(ctx, file)
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) newWriter(filename string) (string, io.WriteCloser, error) {
|
||||
now := time.Now()
|
||||
filePath := filepath.Join(cast.ToString(now.Year()), cast.ToString(int(now.Month())), cast.ToString(now.Day()), cast.ToString(now.Hour()), filename)
|
||||
fileAbsPath := filepath.Join(config.GetFileConfig().BasePath, filePath)
|
||||
|
||||
// 目录不存在则创建
|
||||
fileDir := filepath.Dir(fileAbsPath)
|
||||
if _, err := os.Stat(fileDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(fileDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 创建目标文件
|
||||
out, err := os.OpenFile(fileAbsPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return filePath, out, nil
|
||||
}
|
||||
|
||||
func (f *fileAppImpl) remove(ctx context.Context, file *entity.File) error {
|
||||
if err := os.Remove(filepath.Join(config.GetFileConfig().BasePath, file.Path)); err != nil {
|
||||
logx.ErrorfContext(ctx, "删除旧文件[%s] 失败: %s", file.Path, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
24
server/internal/file/config/config.go
Normal file
24
server/internal/file/config/config.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
sysapp "mayfly-go/internal/sys/application"
|
||||
|
||||
"github.com/may-fly/cast"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigKeyFile string = "FileConfig" // 文件配置key
|
||||
)
|
||||
|
||||
type FileConfig struct {
|
||||
BasePath string // 文件基础路径
|
||||
}
|
||||
|
||||
func GetFileConfig() *FileConfig {
|
||||
c := sysapp.GetConfigApp().GetConfig(ConfigKeyFile)
|
||||
jm := c.GetJsonMap()
|
||||
|
||||
fc := new(FileConfig)
|
||||
fc.BasePath = cast.ToStringD(jm["basePath"], "./file")
|
||||
return fc
|
||||
}
|
||||
16
server/internal/file/domain/entity/file.go
Normal file
16
server/internal/file/domain/entity/file.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package entity
|
||||
|
||||
import "mayfly-go/pkg/model"
|
||||
|
||||
type File struct {
|
||||
model.Model
|
||||
|
||||
FileKey string `json:"fikeKey"` // 文件key
|
||||
Filename string `json:"filename"` // 文件名
|
||||
Path string `json:"path" ` // 文件路径
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (a *File) TableName() string {
|
||||
return "t_sys_file"
|
||||
}
|
||||
5
server/internal/file/domain/entity/query.go
Normal file
5
server/internal/file/domain/entity/query.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package entity
|
||||
|
||||
type FileQuery struct {
|
||||
Keys []string
|
||||
}
|
||||
10
server/internal/file/domain/repository/file.go
Normal file
10
server/internal/file/domain/repository/file.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type File interface {
|
||||
base.Repo[*entity.File]
|
||||
}
|
||||
15
server/internal/file/infrastructure/persistence/file.go
Normal file
15
server/internal/file/infrastructure/persistence/file.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/domain/entity"
|
||||
"mayfly-go/internal/file/domain/repository"
|
||||
"mayfly-go/pkg/base"
|
||||
)
|
||||
|
||||
type fileRepoImpl struct {
|
||||
base.RepoImpl[*entity.File]
|
||||
}
|
||||
|
||||
func newFileRepo() repository.File {
|
||||
return &fileRepoImpl{base.RepoImpl[*entity.File]{M: new(entity.File)}}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"mayfly-go/pkg/ioc"
|
||||
)
|
||||
|
||||
func InitIoc() {
|
||||
ioc.Register(newFileRepo(), ioc.WithComponentName("FileRepo"))
|
||||
}
|
||||
16
server/internal/file/init/init.go
Normal file
16
server/internal/file/init/init.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"mayfly-go/initialize"
|
||||
"mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/file/infrastructure/persistence"
|
||||
"mayfly-go/internal/file/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initialize.AddInitIocFunc(func() {
|
||||
persistence.InitIoc()
|
||||
application.InitIoc()
|
||||
})
|
||||
initialize.AddInitRouterFunc(router.Init)
|
||||
}
|
||||
29
server/internal/file/router/file.go
Normal file
29
server/internal/file/router/file.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"mayfly-go/internal/file/api"
|
||||
"mayfly-go/pkg/biz"
|
||||
"mayfly-go/pkg/ioc"
|
||||
"mayfly-go/pkg/req"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func InitFileRouter(router *gin.RouterGroup) {
|
||||
file := router.Group("sys/files")
|
||||
f := new(api.File)
|
||||
biz.ErrIsNil(ioc.Inject(f))
|
||||
|
||||
reqs := [...]*req.Conf{
|
||||
|
||||
req.NewGet("/detail/:keys", f.GetFileByKeys).DontNeedToken(),
|
||||
|
||||
req.NewGet("/:key", f.GetFileContent).DontNeedToken().NoRes(),
|
||||
|
||||
req.NewPost("/upload", f.Upload).Log(req.NewLogSave("file-文件上传")),
|
||||
|
||||
req.NewDelete("/:key", f.Remove).Log(req.NewLogSave("file-文件删除")),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(file, reqs[:])
|
||||
}
|
||||
9
server/internal/file/router/router.go
Normal file
9
server/internal/file/router/router.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Init(router *gin.RouterGroup) {
|
||||
InitFileRouter(router)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"mayfly-go/internal/common/consts"
|
||||
"mayfly-go/internal/event"
|
||||
@@ -9,7 +8,6 @@ import (
|
||||
"mayfly-go/internal/machine/api/vo"
|
||||
"mayfly-go/internal/machine/application"
|
||||
"mayfly-go/internal/machine/application/dto"
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/guac"
|
||||
"mayfly-go/internal/machine/mcm"
|
||||
@@ -25,8 +23,6 @@ import (
|
||||
"mayfly-go/pkg/utils/collx"
|
||||
"mayfly-go/pkg/ws"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -243,15 +239,6 @@ func (m *Machine) MachineTermOpRecords(rc *req.Ctx) {
|
||||
rc.ResData = res
|
||||
}
|
||||
|
||||
func (m *Machine) MachineTermOpRecord(rc *req.Ctx) {
|
||||
termOp, err := m.MachineTermOpApp.GetById(uint64(rc.PathParamInt("recId")))
|
||||
biz.ErrIsNil(err)
|
||||
|
||||
bytes, err := os.ReadFile(path.Join(config.GetMachine().TerminalRecPath, termOp.RecordFilePath))
|
||||
biz.ErrIsNilAppendErr(err, "读取终端操作记录失败: %s")
|
||||
rc.ResData = base64.StdEncoding.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
const (
|
||||
SocketTimeout = 15 * time.Second
|
||||
MaxGuacMessage = 8192
|
||||
|
||||
@@ -3,6 +3,7 @@ package application
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
fileapp "mayfly-go/internal/file/application"
|
||||
"mayfly-go/internal/machine/config"
|
||||
"mayfly-go/internal/machine/domain/entity"
|
||||
"mayfly-go/internal/machine/domain/repository"
|
||||
@@ -15,8 +16,7 @@ import (
|
||||
"mayfly-go/pkg/scheduler"
|
||||
"mayfly-go/pkg/utils/jsonx"
|
||||
"mayfly-go/pkg/utils/stringx"
|
||||
"os"
|
||||
"path"
|
||||
"mayfly-go/pkg/utils/timex"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -38,6 +38,7 @@ type machineTermOpAppImpl struct {
|
||||
base.AppImpl[*entity.MachineTermOp, repository.MachineTermOp]
|
||||
|
||||
machineCmdConfApp MachineCmdConf `inject:"MachineCmdConfApp"`
|
||||
fileApp fileapp.File `inject:"FileApp"`
|
||||
}
|
||||
|
||||
// 注入MachineTermOpRepo
|
||||
@@ -63,20 +64,14 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
|
||||
termOpRecord.MachineId = cli.Info.Id
|
||||
termOpRecord.Username = cli.Info.Username
|
||||
|
||||
// 回放文件路径为: 基础配置路径/机器编号/操作日期(202301)/day/hour/randstr.cast
|
||||
recRelPath := path.Join(cli.Info.Code, now.Format("200601"), fmt.Sprintf("%d", now.Day()), fmt.Sprintf("%d", now.Hour()))
|
||||
// 文件绝对路径
|
||||
recAbsPath := path.Join(config.GetMachine().TerminalRecPath, recRelPath)
|
||||
os.MkdirAll(recAbsPath, 0766)
|
||||
filename := fmt.Sprintf("%s.cast", stringx.RandByChars(18, stringx.LowerChars))
|
||||
f, err := os.OpenFile(path.Join(recAbsPath, filename), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
|
||||
fileKey, wc, saveFileFunc, err := m.fileApp.NewWriter(ctx, "", fmt.Sprintf("mto_%d_%s.cast", termOpRecord.MachineId, timex.TimeNo()))
|
||||
if err != nil {
|
||||
return errorx.NewBiz("创建终端回放记录文件失败: %s", err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
defer saveFileFunc()
|
||||
|
||||
termOpRecord.RecordFilePath = path.Join(recRelPath, filename)
|
||||
recorder = mcm.NewRecorder(f)
|
||||
termOpRecord.FileKey = fileKey
|
||||
recorder = mcm.NewRecorder(wc)
|
||||
}
|
||||
|
||||
createTsParam := &mcm.CreateTerminalSessionParam{
|
||||
@@ -134,9 +129,8 @@ func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
|
||||
return
|
||||
}
|
||||
|
||||
basePath := config.GetMachine().TerminalRecPath
|
||||
for _, termOp := range termOps {
|
||||
if err := m.DeleteTermOp(basePath, termOp); err != nil {
|
||||
if err := m.DeleteTermOp(termOp); err != nil {
|
||||
logx.Warnf("删除终端操作记录失败: %s", err.Error())
|
||||
}
|
||||
}
|
||||
@@ -144,10 +138,10 @@ func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
|
||||
}
|
||||
|
||||
// 删除终端记录即对应文件
|
||||
func (m *machineTermOpAppImpl) DeleteTermOp(basePath string, termOp *entity.MachineTermOp) error {
|
||||
func (m *machineTermOpAppImpl) DeleteTermOp(termOp *entity.MachineTermOp) error {
|
||||
if err := m.DeleteById(context.Background(), termOp.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Remove(path.Join(basePath, termOp.RecordFilePath))
|
||||
return m.fileApp.Remove(context.TODO(), termOp.FileKey)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ const (
|
||||
)
|
||||
|
||||
type Machine struct {
|
||||
TerminalRecPath string // 终端操作记录存储位置
|
||||
UploadMaxFileSize int64 // 允许上传的最大文件size
|
||||
TermOpSaveDays int // 终端记录保存天数
|
||||
GuacdHost string // guacd服务地址 默认 127.0.0.1
|
||||
GuacdPort int // guacd服务端口 默认 4822
|
||||
GuacdFilePath string // guacd服务文件存储位置,用于挂载RDP文件夹
|
||||
GuacdRecPath string // guacd服务记录存储位置,用于记录rdp操作记录
|
||||
}
|
||||
|
||||
// 获取机器相关配置
|
||||
@@ -29,12 +27,6 @@ func GetMachine() *Machine {
|
||||
|
||||
mc := new(Machine)
|
||||
|
||||
terminalRecPath := jm["terminalRecPath"]
|
||||
if terminalRecPath == "" {
|
||||
terminalRecPath = "./rec"
|
||||
}
|
||||
mc.TerminalRecPath = terminalRecPath
|
||||
|
||||
// 将1GB等字符串转为int64的byte
|
||||
uploadMaxFileSizeStr := jm["uploadMaxFileSize"]
|
||||
var uploadMaxFileSize int64 = 1 * bytex.GB
|
||||
@@ -51,7 +43,6 @@ func GetMachine() *Machine {
|
||||
mc.GuacdHost = cast.ToString(jm["guacdHost"])
|
||||
mc.GuacdPort = cast.ToIntD(jm["guacdPort"], 4822)
|
||||
mc.GuacdFilePath = cast.ToStringD(jm["guacdFilePath"], "")
|
||||
mc.GuacdRecPath = cast.ToStringD(jm["guacdRecPath"], "")
|
||||
|
||||
return mc
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
type MachineTermOp struct {
|
||||
model.DeletedModel
|
||||
|
||||
MachineId uint64 `json:"machineId"`
|
||||
Username string `json:"username"`
|
||||
RecordFilePath string `json:"recordFilePath"` // 回放文件路径
|
||||
ExecCmds string `json:"execCmds"` // 执行的命令
|
||||
MachineId uint64 `json:"machineId"`
|
||||
Username string `json:"username"`
|
||||
FileKey string `json:"fileKey"` // 文件key
|
||||
ExecCmds string `json:"execCmds"` // 执行的命令
|
||||
|
||||
CreateTime *time.Time `json:"createTime"`
|
||||
CreatorId uint64 `json:"creatorId"`
|
||||
|
||||
@@ -47,9 +47,6 @@ func InitMachineRouter(router *gin.RouterGroup) {
|
||||
|
||||
// 获取机器终端回放记录列表,目前具有保存机器信息的权限标识才有权限查看终端回放
|
||||
req.NewGet(":machineId/term-recs", m.MachineTermOpRecords).RequiredPermission(saveMachineP),
|
||||
|
||||
// 获取机器终端回放记录
|
||||
req.NewGet(":machineId/term-recs/:recId", m.MachineTermOpRecord).RequiredPermission(saveMachineP),
|
||||
}
|
||||
|
||||
req.BatchSetGroup(machines, reqs[:])
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
_ "mayfly-go/internal/auth/init"
|
||||
_ "mayfly-go/internal/common/init"
|
||||
_ "mayfly-go/internal/db/init"
|
||||
_ "mayfly-go/internal/file/init"
|
||||
_ "mayfly-go/internal/flow/init"
|
||||
_ "mayfly-go/internal/machine/init"
|
||||
_ "mayfly-go/internal/mongo/init"
|
||||
|
||||
@@ -9,10 +9,16 @@ import (
|
||||
|
||||
const DefaultDateTimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// DefaultFormat 使用默认格式进行格式化: 2006-01-02 15:04:05
|
||||
func DefaultFormat(time time.Time) string {
|
||||
return time.Format(DefaultDateTimeFormat)
|
||||
}
|
||||
|
||||
// TimeNo 获取当前时间编号,格式为20060102150405
|
||||
func TimeNo() string {
|
||||
return time.Now().Format("20060102150405")
|
||||
}
|
||||
|
||||
func NewNullTime(t time.Time) NullTime {
|
||||
return NullTime{
|
||||
NullTime: sql.NullTime{
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type FileWriter struct {
|
||||
tryFlushCount int
|
||||
writer *os.File
|
||||
aborted bool
|
||||
}
|
||||
|
||||
func NewFileWriter(filePath string) *FileWriter {
|
||||
if filePath == "" {
|
||||
panic("filePath is empty")
|
||||
}
|
||||
|
||||
// 使用filepath.Dir函数提取文件夹路径
|
||||
dir := filepath.Dir(filePath)
|
||||
if dir != "" {
|
||||
// 检查文件夹路径,不存在则创建
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fw, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &FileWriter{writer: fw}
|
||||
}
|
||||
|
||||
func (f *FileWriter) Close() {
|
||||
f.writer.Close()
|
||||
}
|
||||
|
||||
func (f *FileWriter) TryFlush() {
|
||||
}
|
||||
func (f *FileWriter) Write(b []byte) (n int, err error) {
|
||||
return f.writer.Write(b)
|
||||
}
|
||||
|
||||
func (f *FileWriter) WriteString(data string) {
|
||||
io.WriteString(f.writer, data)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package writer
|
||||
|
||||
import "io"
|
||||
|
||||
type CustomWriter interface {
|
||||
io.Writer
|
||||
WriteString(data string)
|
||||
Close()
|
||||
TryFlush()
|
||||
}
|
||||
26
server/pkg/utils/writerx/counting_writer.go
Normal file
26
server/pkg/utils/writerx/counting_writer.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package writerx
|
||||
|
||||
import "io"
|
||||
|
||||
type CountingWriteCloser struct {
|
||||
w io.WriteCloser
|
||||
n int64 // 已写入的字节数
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) Write(p []byte) (int, error) {
|
||||
n, err := c.w.Write(p)
|
||||
c.n += int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) Close() error {
|
||||
return c.w.Close()
|
||||
}
|
||||
|
||||
func (c *CountingWriteCloser) BytesWritten() int64 {
|
||||
return c.n
|
||||
}
|
||||
|
||||
func NewCountingWriteCloser(writer io.WriteCloser) *CountingWriteCloser {
|
||||
return &CountingWriteCloser{w: writer}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package writer
|
||||
package writerx
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
@@ -38,8 +38,8 @@ func (g *GzipWriter) Write(p []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (g *GzipWriter) Close() {
|
||||
g.writer.Close()
|
||||
func (g *GzipWriter) Close() error {
|
||||
return g.writer.Close()
|
||||
}
|
||||
|
||||
func (g *GzipWriter) TryFlush() {
|
||||
21
server/pkg/utils/writerx/string_writer.go
Normal file
21
server/pkg/utils/writerx/string_writer.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package writerx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type StringWriter struct {
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
func (sw *StringWriter) WriteString(s string) (n int, err error) {
|
||||
return sw.WriteCloser.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (sw *StringWriter) Close() error {
|
||||
return sw.WriteCloser.Close()
|
||||
}
|
||||
|
||||
func NewStringWriter(writer io.WriteCloser) *StringWriter {
|
||||
return &StringWriter{WriteCloser: writer}
|
||||
}
|
||||
Binary file not shown.
@@ -101,8 +101,7 @@ CREATE TABLE `t_db_transfer_files` (
|
||||
`task_id` bigint COMMENT '迁移任务ID',
|
||||
`log_id` bigint COMMENT '日志ID',
|
||||
`file_db_type` varchar(200) COMMENT 'sql文件数据库类型',
|
||||
`file_name` varchar(200) COMMENT '显式文件名 默认: 年月日时分秒.zip',
|
||||
`file_uuid` varchar(50) COMMENT '文件真实uuid,拼接后可以下载',
|
||||
`file_key` varchar(50) COMMENT '文件',
|
||||
PRIMARY KEY (id)
|
||||
) COMMENT '数据库迁移文件管理';
|
||||
|
||||
@@ -668,11 +667,11 @@ INSERT INTO `t_sys_config` (name, `key`, params, value, remark, create_time, cre
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('oauth2登录配置', 'Oauth2Login', '[{"name":"是否启用","model":"enable","placeholder":"是否启用oauth2登录","options":"true,false"},{"name":"名称","model":"name","placeholder":"oauth2名称"},{"name":"Client ID","model":"clientId","placeholder":"Client ID"},{"name":"Client Secret","model":"clientSecret","placeholder":"Client Secret"},{"name":"Authorization URL","model":"authorizationURL","placeholder":"Authorization URL"},{"name":"AccessToken URL","model":"accessTokenURL","placeholder":"AccessToken URL"},{"name":"Redirect URL","model":"redirectURL","placeholder":"本系统地址"},{"name":"Scopes","model":"scopes","placeholder":"Scopes"},{"name":"Resource URL","model":"resourceURL","placeholder":"获取用户信息资源地址"},{"name":"UserIdentifier","model":"userIdentifier","placeholder":"用户唯一标识字段;格式为type:fieldPath(string:username)"},{"name":"是否自动注册","model":"autoRegister","placeholder":"","options":"true,false"}]', '', 'oauth2登录相关配置信息', 'admin,', '2023-07-22 13:58:51', 1, 'admin', '2023-07-22 19:34:37', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('ldap登录配置', 'LdapLogin', '[{"name":"是否启用","model":"enable","placeholder":"是否启用","options":"true,false"},{"name":"host","model":"host","placeholder":"host"},{"name":"port","model":"port","placeholder":"port"},{"name":"bindDN","model":"bindDN","placeholder":"LDAP 服务的管理员账号,如: \\"cn=admin,dc=example,dc=com\\""},{"name":"bindPwd","model":"bindPwd","placeholder":"LDAP 服务的管理员密码"},{"name":"baseDN","model":"baseDN","placeholder":"用户所在的 base DN, 如: \\"ou=users,dc=example,dc=com\\""},{"name":"userFilter","model":"userFilter","placeholder":"过滤用户的方式, 如: \\"(uid=%s)、(&(objectClass=organizationalPerson)(uid=%s))\\""},{"name":"uidMap","model":"uidMap","placeholder":"用户id和 LDAP 字段名之间的映射关系,如: cn"},{"name":"udnMap","model":"udnMap","placeholder":"用户姓名(dispalyName)和 LDAP 字段名之间的映射关系,如: displayName"},{"name":"emailMap","model":"emailMap","placeholder":"用户email和 LDAP 字段名之间的映射关系"},{"name":"skipTLSVerify","model":"skipTLSVerify","placeholder":"客户端是否跳过 TLS 证书验证","options":"true,false"},{"name":"安全协议","model":"securityProtocol","placeholder":"安全协议(为Null不使用安全协议),如: StartTLS, LDAPS","options":"Null,StartTLS,LDAPS"}]', '', 'ldap登录相关配置', 'admin,', '2023-08-25 21:47:20', 1, 'admin', '2023-08-25 22:56:07', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('系统全局样式设置', 'SysStyleConfig', '[{"model":"logoIcon","name":"logo图标","placeholder":"系统logo图标(base64编码, 建议svg格式,不超过10k)","required":false},{"model":"title","name":"菜单栏标题","placeholder":"系统菜单栏标题展示","required":false},{"model":"viceTitle","name":"登录页标题","placeholder":"登录页标题展示","required":false},{"model":"useWatermark","name":"是否启用水印","placeholder":"是否启用系统水印","options":"true,false","required":false},{"model":"watermarkContent","name":"水印补充信息","placeholder":"额外水印信息","required":false}]', '{"title":"mayfly-go","viceTitle":"mayfly-go","logoIcon":"","useWatermark":"true","watermarkContent":""}', '系统icon、标题、水印信息等配置', 'all', '2024-01-04 15:17:18', 1, 'admin', '2024-01-05 09:40:44', 1, 'admin', 0, NULL);
|
||||
INSERT INTO t_sys_config ( name, `key`, params, value, remark, permission, create_time, creator_id, creator, update_time, modifier_id, modifier, is_deleted, delete_time) VALUES('机器相关配置', 'MachineConfig', '[{"name":"终端回放存储路径","model":"terminalRecPath","placeholder":"终端回放存储路径"},{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"},{"name":"guacd服务记录存储位置","model":"guacdRecPath","placeholder":"guacd服务记录存储位置,用于记录rdp操作记录"}]', '{"terminalRecPath":"./rec","uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file","guacdRecPath":"./guacd/rdp-rec"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-04-06 12:25:03', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`id`, `name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES(10, '数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"},{"model":"transferPath","name":"迁移路径","placeholder":"数据库迁移文件存储路径"}]', '{"backupPath":"./db/backup","transferPath":"./db/transfer"}', '数据库备份恢复', 'all', '2023-12-29 09:55:26', 1, 'admin', '2024-08-27 15:22:22', 12, 'liuzongyang', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('机器相关配置', 'MachineConfig', '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]', '{"uploadMaxFileSize":"1000MB","termOpSaveDays":"30","guacdHost":"","guacdPort":"","guacdFilePath":"./guacd/rdp-file"}', '机器相关配置,如终端回放路径等', 'all', '2023-07-13 16:26:44', 1, 'admin', '2024-10-21 17:02:55', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('Mysql可执行文件', 'MysqlBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mysql/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('MariaDB可执行文件', 'MariadbBin', '[{"model":"path","name":"路径","placeholder":"可执行文件路径","required":true},{"model":"mysql","name":"mysql","placeholder":"mysql命令路径(空则为 路径/mysql)","required":false},{"model":"mysqldump","name":"mysqldump","placeholder":"mysqldump命令路径(空则为 路径/mysqldump)","required":false},{"model":"mysqlbinlog","name":"mysqlbinlog","placeholder":"mysqlbinlog命令路径(空则为 路径/mysqlbinlog)","required":false}]', '{"mysql":"","mysqldump":"","mysqlbinlog":"","path":"./db/mariadb/bin"}', '', 'admin,', '2023-12-29 10:01:33', 1, 'admin', '2023-12-29 13:34:40', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('DBMS配置', 'DbmsConfig', '[{"model":"querySqlSave","name":"记录查询sql","placeholder":"是否记录查询类sql","options":"true,false"},{"model":"maxResultSet","name":"最大结果集","placeholder":"允许sql查询的最大结果集数。注: 0=不限制","options":""},{"model":"sqlExecTl","name":"sql执行时间限制","placeholder":"超过该时间(单位:秒),执行将被取消"}]', '{"querySqlSave":"false","maxResultSet":"0","sqlExecTl":"60"}', 'DBMS相关配置', 'admin,', '2024-03-06 13:30:51', 1, 'admin', '2024-03-06 14:07:16', 1, 'admin', 0, NULL);
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('文件配置', 'FileConfig', '[{"model":"basePath","name":"基础路径","placeholder":"默认为可执行文件对应目录下./file"}]', '{"basePath":"./file"}', '系统文件相关配置', 'admin,', '2024-10-20 22:30:01', 1, 'admin', '2024-10-21 13:51:17', 1, 'admin', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
@@ -856,7 +855,6 @@ INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `we
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724376022, 1709194669, 2, 1, '文件-删除', 'db:transfer:files:del', 1724376022, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 09:20:23', '2024-08-23 14:50:21', 'SmLcpu6c/HIURtJJA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724395850, 1709194669, 2, 1, '文件-下载', 'db:transfer:files:down', 1724395850, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 14:50:51', '2024-08-23 14:50:51', 'SmLcpu6c/FmqK4azt/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724398262, 1709194669, 2, 1, '文件', 'db:transfer:files', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 15:31:02', '2024-08-23 15:31:16', 'SmLcpu6c/btVtrbhk/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724817775, 1709194669, 2, 1, '文件-重命名', 'db:transfer:files:rename', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-28 12:02:56', '2024-08-28 12:03:01', 'SmLcpu6c/zu4fvnuA/', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714032002, 1713875842, '12sSjal1/UnWIUhW0/0tJwC3Gf/', 2, 1, '命令配置-删除', 'cmdconf:del', 1714032002, 'null', 1, 'admin', 1, 'admin', '2024-04-25 16:00:02', '2024-04-25 16:00:02', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1714031981, 1713875842, '12sSjal1/UnWIUhW0/tEzIKecl/', 2, 1, '命令配置-保存', 'cmdconf:save', 1714031981, 'null', 1, 'admin', 1, 'admin', '2024-04-25 15:59:41', '2024-04-25 15:59:41', 0, NULL);
|
||||
INSERT INTO t_sys_resource (id, pid, ui_path, `type`, status, name, code, weight, meta, creator_id, creator, modifier_id, modifier, create_time, update_time, is_deleted, delete_time) VALUES(1713875842, 2, '12sSjal1/UnWIUhW0/', 1, 1, '安全配置', 'security', 1713875842, '{"component":"ops/machine/security/SecurityConfList","icon":"Setting","isKeepAlive":true,"routeName":"SecurityConfList"}', 1, 'admin', 1, 'admin', '2024-04-23 20:37:22', '2024-04-23 20:37:22', 0, NULL);
|
||||
@@ -915,6 +913,25 @@ INSERT INTO `t_sys_role_resource` (role_id,resource_id,creator_id,creator,create
|
||||
(7,1,1,'admin','2021-07-06 15:07:09', 0, NULL);
|
||||
COMMIT;
|
||||
|
||||
DROP TABLE IF EXISTS `t_sys_file`;
|
||||
CREATE TABLE `t_sys_file` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`file_key` varchar(32) NOT NULL COMMENT 'key',
|
||||
`filename` varchar(255) NOT NULL COMMENT '文件名',
|
||||
`path` varchar(555) NOT NULL COMMENT '文件路径',
|
||||
`size` int NULL DEFAULT NULL COMMENT '文件大小',
|
||||
`creator_id` bigint NULL DEFAULT NULL,
|
||||
`creator` varchar(32) NULL DEFAULT NULL,
|
||||
`modifier_id` bigint NULL DEFAULT NULL,
|
||||
`modifier` varchar(255) NULL DEFAULT NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`delete_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_file_key` (`file_key`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统文件表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for t_tag_tree
|
||||
-- ----------------------------
|
||||
|
||||
@@ -7,12 +7,12 @@ ALTER TABLE `t_db_data_sync_task`
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724376022, 1709194669, 2, 1, '文件-删除', 'db:transfer:files:del', 1724376022, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 09:20:23', '2024-08-23 14:50:21', 'SmLcpu6c/HIURtJJA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724395850, 1709194669, 2, 1, '文件-下载', 'db:transfer:files:down', 1724395850, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 14:50:51', '2024-08-23 14:50:51', 'SmLcpu6c/FmqK4azt/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724398262, 1709194669, 2, 1, '文件', 'db:transfer:files', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-23 15:31:02', '2024-08-23 15:31:16', 'SmLcpu6c/btVtrbhk/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724817775, 1709194669, 2, 1, '文件-重命名', 'db:transfer:files:rename', 1724376021, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-28 12:02:56', '2024-08-28 12:03:01', 'SmLcpu6c/zu4fvnuA/', 0, NULL);
|
||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `ui_path`, `is_deleted`, `delete_time`) VALUES(1724998419, 1709194669, 2, 1, '文件-执行', 'db:transfer:files:run', 1724998419, 'null', 12, 'liuzongyang', 12, 'liuzongyang', '2024-08-30 14:13:39', '2024-08-30 14:13:39', 'SmLcpu6c/qINungml/', 0, NULL);
|
||||
|
||||
-- 新增数据库迁移相关的系统配置
|
||||
DELETE FROM `t_sys_config` WHERE `key` = 'DbBackupRestore';
|
||||
INSERT INTO `t_sys_config` (`id`, `name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES(10, '数据库备份恢复', 'DbBackupRestore', '[{"model":"backupPath","name":"备份路径","placeholder":"备份文件存储路径"},{"model":"transferPath","name":"迁移路径","placeholder":"数据库迁移文件存储路径"}]', '{"backupPath":"./db/backup","transferPath":"./db/transfer"}', '数据库备份恢复', 'all', '2023-12-29 09:55:26', 1, 'admin', '2024-08-27 15:22:22', 12, 'liuzongyang', 0, NULL);
|
||||
UPDATE `t_sys_config` SET param = '[{"name":"uploadMaxFileSize","model":"uploadMaxFileSize","placeholder":"允许上传的最大文件大小(1MB、2GB等)"},{"model":"termOpSaveDays","name":"终端记录保存时间","placeholder":"终端记录保存时间(单位天)"},{"model":"guacdHost","name":"guacd服务ip","placeholder":"guacd服务ip,默认 127.0.0.1","required":false},{"name":"guacd服务端口","model":"guacdPort","placeholder":"guacd服务端口,默认 4822","required":false},{"model":"guacdFilePath","name":"guacd服务文件存储位置","placeholder":"guacd服务文件存储位置,用于挂载RDP文件夹"}]' WHERE `key`='MachineConfig';
|
||||
INSERT INTO `t_sys_config` (`name`, `key`, `params`, `value`, `remark`, `permission`, `create_time`, `creator_id`, `creator`, `update_time`, `modifier_id`, `modifier`, `is_deleted`, `delete_time`) VALUES('文件配置', 'FileConfig', '[{"model":"basePath","name":"基础路径","placeholder":"默认为可执行文件对应目录下./file"}]', '{"basePath":"./file"}', '系统文件相关配置', 'admin,', '2024-10-20 22:30:01', 1, 'admin', '2024-10-21 13:51:17', 1, 'admin', 0, NULL);
|
||||
|
||||
-- 数据库迁移到文件
|
||||
ALTER TABLE `t_db_transfer_task`
|
||||
@@ -37,11 +37,28 @@ CREATE TABLE `t_db_transfer_files` (
|
||||
`task_id` bigint COMMENT '迁移任务ID',
|
||||
`log_id` bigint COMMENT '日志ID',
|
||||
`file_db_type` varchar(200) COMMENT 'sql文件数据库类型',
|
||||
`file_name` varchar(200) COMMENT '显式文件名 默认: 年月日时分秒.zip',
|
||||
`file_uuid` varchar(50) COMMENT '文件真实uuid,拼接后可以下载',
|
||||
`file_key` varchar(50) COMMENT '文件',
|
||||
PRIMARY KEY (id)
|
||||
) COMMENT '数据库迁移文件管理';
|
||||
|
||||
|
||||
ALTER TABLE `t_flow_procdef`
|
||||
ADD COLUMN `condition` text NULL comment '触发审批的条件(计算结果返回1则需要启用该流程)';
|
||||
ADD COLUMN `condition` text NULL comment '触发审批的条件(计算结果返回1则需要启用该流程)';
|
||||
|
||||
CREATE TABLE `t_sys_file` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`file_key` varchar(32) NOT NULL COMMENT 'key',
|
||||
`filename` varchar(255) NOT NULL COMMENT '文件名',
|
||||
`path` varchar(255) NOT NULL COMMENT '文件路径',
|
||||
`size` int NULL DEFAULT NULL COMMENT '文件大小',
|
||||
`creator_id` bigint NULL DEFAULT NULL,
|
||||
`creator` varchar(32) NULL DEFAULT NULL,
|
||||
`modifier_id` bigint NULL DEFAULT NULL,
|
||||
`modifier` varchar(255) NULL DEFAULT NULL,
|
||||
`create_time` datetime NULL DEFAULT NULL,
|
||||
`update_time` datetime NULL DEFAULT NULL,
|
||||
`is_deleted` tinyint NOT NULL DEFAULT 0,
|
||||
`delete_time` datetime NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
KEY `idx_file_key` (`file_key`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '系统文件表';
|
||||
Reference in New Issue
Block a user