refactor: code review

This commit is contained in:
meilin.huang
2023-12-29 16:48:15 +08:00
parent 664118a709
commit 76fd6675b5
35 changed files with 343 additions and 349 deletions

View File

@@ -33,7 +33,7 @@
"splitpanes": "^3.1.5",
"sql-formatter": "^14.0.0",
"uuid": "^9.0.1",
"vue": "^3.3.12",
"vue": "^3.4.0",
"vue-router": "^4.2.5",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",

View File

@@ -24,7 +24,7 @@ export function dateStrFormat(fmt: string, dateStr: string) {
}
export function dateFormat(dateStr: string) {
if (dateStr.startsWith('0001-01-01', 0)) {
if (dateStr?.startsWith('0001-01-01', 0)) {
return '';
}
return dateFormat2('yyyy-MM-dd HH:mm:ss', new Date(dateStr));

View File

@@ -28,17 +28,13 @@
<script setup lang="ts" name="SearchFormItem">
import { computed } from 'vue';
import { SearchItem } from '../index';
import { useVModel } from '@vueuse/core';
interface SearchFormItemProps {
modelValue: any;
item: SearchItem;
}
const props = defineProps<SearchFormItemProps>();
const emit = defineEmits(['update:modelValue']);
const itemValue = useVModel(props, 'modelValue', emit);
const itemValue = defineModel('modelValue');
// 判断 fieldNames 设置 label && value && children 的 key 值
const fieldNames = computed(() => {

View File

@@ -44,11 +44,9 @@ import Grid from '@/components/Grid/index.vue';
import GridItem from '@/components/Grid/components/GridItem.vue';
import SvgIcon from '@/components/svgIcon/index.vue';
import { SearchItem } from './index';
import { useVModel } from '@vueuse/core';
interface ProTableProps {
items: SearchItem[]; // 搜索配置项
modelValue?: { [key: string]: any }; // 搜索参数
searchCol: number | Record<BreakPoint, number>;
search: (params: any) => void; // 搜索方法
reset: (params: any) => void; // 重置方法
@@ -60,9 +58,7 @@ const props = withDefaults(defineProps<ProTableProps>(), {
modelValue: () => ({}),
});
const emit = defineEmits(['update:modelValue']);
const searchParam = useVModel(props, 'modelValue', emit);
const searchParam: any = defineModel('modelValue');
// 获取响应式设置
const getResponsive = (item: SearchItem) => {

View File

@@ -1,10 +1,10 @@
<template>
<div class="dynamic-form">
<el-form v-bind="$attrs" ref="formRef" :model="formData" label-width="auto">
<el-form-item v-for="item in formItems as any" :key="item.name" :prop="item.model" :label="item.name" required>
<el-input v-if="!item.options" v-model="formData[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-form v-bind="$attrs" ref="formRef" :model="modelValue" label-width="auto">
<el-form-item v-for="item in props.formItems as any" :key="item.name" :prop="item.model" :label="item.name" :required="item.required ?? true">
<el-input v-if="!item.options" v-model="modelValue[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-select v-else v-model="formData[item.model]" :placeholder="item.placeholder" filterable autocomplete="off" clearable style="width: 100%">
<el-select v-else v-model="modelValue[item.model]" :placeholder="item.placeholder" filterable autocomplete="off" clearable style="width: 100%">
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
</el-select>
</el-form-item>
@@ -13,19 +13,15 @@
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core';
import { ref } from 'vue';
const props = defineProps({
formItems: { type: Array },
modelValue: { type: Object },
});
const emit = defineEmits(['update:modelValue']);
const formRef: any = ref();
const formData: any = useVModel(props, 'modelValue', emit);
const modelValue: any = defineModel();
const validate = async (func: any) => {
await formRef.value.validate(func);

View File

@@ -1,7 +1,7 @@
<template>
<div class="form-dialog">
<el-dialog @close="close" v-bind="$attrs" :title="title" v-model="dialogVisible" :width="width">
<dynamic-form ref="df" :form-items="formItems" v-model="formData" />
<dynamic-form ref="df" :form-items="props.formItems" v-model="formData" />
<template #footer>
<span>
@@ -18,22 +18,19 @@
<script lang="ts" setup>
import { ref } from 'vue';
import DynamicForm from './DynamicForm.vue';
import { useVModel } from '@vueuse/core';
const emit = defineEmits(['update:visible', 'update:modelValue', 'close', 'confirm']);
const emit = defineEmits(['close', 'confirm']);
const props = defineProps({
title: { type: String },
visible: { type: Boolean },
width: { type: [String, Number], default: '500px' },
formItems: { type: Array },
modelValue: { type: Object },
});
const df: any = ref();
const formData: any = useVModel(props, 'modelValue', emit);
const dialogVisible: any = useVModel(props, 'visible', emit);
const formData: any = defineModel('modelValue');
const dialogVisible = defineModel<boolean>('visible', { default: false });
const close = () => {
emit('close');

View File

@@ -29,6 +29,12 @@
</template>
</el-table-column>
<el-table-column prop="required" label="必填" min-width="40px">
<template #default="scope">
<el-checkbox v-model="scope.row['required']" />
</template>
</el-table-column>
<el-table-column label="操作" wdith="20px">
<template #default="scope">
<el-button type="danger" @click="deleteItem(scope.$index)" icon="delete" plain></el-button>
@@ -39,15 +45,7 @@
</template>
<script lang="ts" setup>
import { useVModel } from '@vueuse/core';
const props = defineProps({
modelValue: { type: Array },
});
const emit = defineEmits(['update:modelValue']);
const formItems: any = useVModel(props, 'modelValue', emit);
const formItems: any = defineModel('modelValue');
const addItem = () => {
formItems.value.push({});

View File

@@ -2,7 +2,7 @@
<div>
<transition name="el-zoom-in-top">
<!-- 查询表单 -->
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm_" :search="search" :reset="reset" :search-col="searchCol">
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm" :search="search" :reset="reset" :search-col="searchCol">
<!-- 遍历父组件传入的 solts 透传给子组件 -->
<template v-for="(_, key) in useSlots()" v-slot:[key]>
<slot :name="key"></slot>
@@ -47,7 +47,7 @@
@keyup.enter.native="searchFormItemKeyUpEnter"
v-if="!nowSearchItem.slot"
:item="nowSearchItem"
v-model="queryForm_[nowSearchItem.prop]"
v-model="queryForm[nowSearchItem.prop]"
/>
<slot @keyup.enter.native="searchFormItemKeyUpEnter" v-else :name="nowSearchItem.slot"></slot>
@@ -161,8 +161,8 @@
style="text-align: right"
layout="prev, pager, next, total, sizes, jumper"
:total="total"
v-model:current-page="queryForm_.pageNum"
v-model:page-size="queryForm_.pageSize"
v-model:current-page="queryForm.pageNum"
v-model:page-size="queryForm.pageSize"
:page-sizes="pageSizes"
/>
</el-row>
@@ -176,7 +176,7 @@ import { TableColumn } from './index';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { useThemeConfig } from '@/store/themeConfig';
import { storeToRefs } from 'pinia';
import { useVModel, useEventListener } from '@vueuse/core';
import { useEventListener } from '@vueuse/core';
import Api from '@/common/Api';
import SearchForm from '@/components/SearchForm/index.vue';
import { SearchItem } from '../SearchForm/index';
@@ -200,7 +200,6 @@ export interface PageTableProps {
beforeQueryFn?: (params: any) => any; // 执行查询时对查询参数进行处理,调整等
dataHandlerFn?: (data: any) => any; // 数据处理回调函数,用于将请求回来的数据二次加工处理等
searchItems?: SearchItem[];
queryForm?: any; // 查询表单参数 ==> 非必传(默认为{pageNum:1, pageSize: 10}
border?: boolean; // 是否带有纵向边框 ==> 非必传默认为false
toolButton?: ('setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传默认为true
searchCol?: any; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } | number 如 3
@@ -212,10 +211,6 @@ const props = withDefaults(defineProps<PageTableProps>(), {
pageable: true,
showSelection: false,
lazy: false,
queryForm: {
pageNum: 1,
pageSize: 0,
},
border: false,
toolButton: true,
showSearch: false,
@@ -223,6 +218,14 @@ const props = withDefaults(defineProps<PageTableProps>(), {
searchCol: () => ({ xs: 1, sm: 3, md: 3, lg: 4, xl: 5 }),
});
// 查询表单参数 ==> 非必传(默认为{pageNum:1, pageSize: 10}
const queryForm: Ref<any> = defineModel('queryForm', {
default: {
pageNum: 1,
pageSize: 0,
},
});
// table 实例
const tableRef = ref<InstanceType<typeof ElTable>>();
@@ -250,16 +253,14 @@ const nowSearchItem: Ref<SearchItem> = ref(null) as any;
*/
const changeSimpleFormItem = (searchItem: SearchItem) => {
// 将之前的值置为空,避免因为只显示一个搜索项却搜索多个条件
queryForm_.value[nowSearchItem.value.prop] = null;
queryForm.value[nowSearchItem.value.prop] = null;
nowSearchItem.value = searchItem;
};
const queryForm_: Ref<any> = useVModel(props, 'queryForm', emit);
const { tableData, total, loading, search, reset, getTableData, handlePageNumChange, handlePageSizeChange } = usePageTable(
props.pageable,
props.pageApi,
queryForm_,
queryForm,
props.beforeQueryFn,
props.dataHandlerFn
);
@@ -295,7 +296,7 @@ onMounted(async () => {
nowSearchItem.value = props.searchItems[0];
}
let pageSize = queryForm_.value.pageSize;
let pageSize = queryForm.value.pageSize;
// 如果pageSize设为0则使用系统全局配置的pageSize
if (!pageSize) {
pageSize = themeConfig.value.defaultListPageSize;
@@ -305,8 +306,8 @@ onMounted(async () => {
}
}
queryForm_.value.pageNum = 1;
queryForm_.value.pageSize = pageSize;
queryForm.value.pageNum = 1;
queryForm.value.pageSize = pageSize;
state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
if (!props.lazy) {

View File

@@ -1,5 +1,5 @@
import Api from '@/common/Api';
import { reactive, toRefs, toValue } from 'vue';
import { isReactive, reactive, toRefs, toValue } from 'vue';
/**
* @description table 页面操作方法封装
@@ -41,8 +41,13 @@ export const usePageTable = (
let sp = toValue(state.searchParams);
if (beforeQueryFn) {
sp = beforeQueryFn(sp);
if (isReactive(state.searchParams)) {
state.searchParams.value = sp;
} else {
state.searchParams = sp;
}
}
let res = await api.request(sp);
dataCallBack && (res = dataCallBack(res));

View File

@@ -46,9 +46,6 @@ import { dbApi } from './api';
import { ElMessage } from 'element-plus';
const props = defineProps({
visible: {
type: Boolean,
},
data: {
type: [Boolean, Object],
},
@@ -61,8 +58,12 @@ const props = defineProps({
},
});
const visible = defineModel<boolean>('visible', {
default: false,
});
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const emit = defineEmits(['cancel', 'val-change']);
const rules = {
dbNames: [
@@ -97,7 +98,7 @@ const state = reactive({
dbId: 0,
dbNames: String,
name: null as any,
intervalDay: 1,
intervalDay: null,
startTime: null as any,
repeated: null as any,
},
@@ -107,9 +108,9 @@ const state = reactive({
editOrCreate: false,
});
watch(props, (newValue: any) => {
if (newValue.visible) {
init(newValue.data);
watch(visible, (newValue: any) => {
if (newValue) {
init(props.data);
}
});
@@ -135,7 +136,7 @@ const init = (data: any) => {
} else {
state.editOrCreate = false;
state.form.name = '';
state.form.intervalDay = 1;
state.form.intervalDay = null;
const now = new Date();
state.form.startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
getDbNamesWithoutBackup();
@@ -150,32 +151,32 @@ const getDbNamesWithoutBackup = async () => {
const btnOk = async () => {
backupForm.value.validate(async (valid: boolean) => {
if (valid) {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.form.repeated = true;
const reqForm = { ...state.form };
let api = dbApi.createDbBackup;
if (props.data) {
api = dbApi.saveDbBackup;
}
api.request(reqForm).then(() => {
try {
state.btnLoading = true;
await api.request(reqForm);
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
ElMessage.error('请正确填写信息');
return false;
} finally {
state.btnLoading = false;
}
});
};
const cancel = () => {
emit('update:visible', false);
visible.value = false;
emit('cancel');
};
</script>

View File

@@ -9,11 +9,7 @@
:searchItems="searchItems"
:before-query-fn="beforeQueryFn"
v-model:query-form="query"
:data="state.data"
:columns="columns"
:total="state.total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
>
<template #dbSelect>
<el-select v-model="query.dbName" placeholder="请选择数据库" style="width: 200px" filterable clearable>
@@ -29,8 +25,8 @@
<template #action="{ data }">
<el-button @click="editDbBackup(data)" type="primary" link>编辑</el-button>
<el-button @click="enableDbBackup(data)" type="primary" link>启用</el-button>
<el-button @click="disableDbBackup(data)" type="primary" link>禁用</el-button>
<el-button @click="enableDbBackup(data)" v-if="!data.enabled" type="success" link>启用</el-button>
<el-button @click="disableDbBackup(data)" v-if="data.enabled" type="warning" link>禁用</el-button>
</template>
</page-table>
@@ -76,7 +72,7 @@ const columns = [
TableColumn.new('enabled', '是否启用'),
TableColumn.new('lastResult', '执行结果'),
TableColumn.new('lastTime', '执行时间').isTime(),
TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter(),
TableColumn.new('action', '操作').isSlot().setMinWidth(160).fixedRight().alignCenter(),
];
const emptyQuery = {

View File

@@ -123,7 +123,7 @@
</el-dialog>
<el-dialog
width="90%"
width="80%"
:title="`${dbBackupDialog.title} - 数据库备份`"
:close-on-click-modal="false"
:destroy-on-close="true"
@@ -133,7 +133,7 @@
</el-dialog>
<el-dialog
width="90%"
width="80%"
:title="`${dbRestoreDialog.title} - 数据库恢复`"
:close-on-click-modal="false"
:destroy-on-close="true"

View File

@@ -29,6 +29,7 @@
:disabled="state.editOrCreate"
@change="changeHistory"
v-model="state.history"
value-key="id"
placeholder="数据库备份"
filterable
clearable
@@ -58,9 +59,6 @@ import { dbApi } from './api';
import { ElMessage } from 'element-plus';
const props = defineProps({
visible: {
type: Boolean,
},
data: {
type: [Boolean, Object],
},
@@ -78,7 +76,11 @@ const props = defineProps({
});
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const emit = defineEmits(['cancel', 'val-change']);
const visible = defineModel<boolean>('visible', {
default: false,
});
const validatePointInTime = (rule: any, value: any, callback: any) => {
if (!state.histories || state.histories.length == 0) {
@@ -161,9 +163,9 @@ onMounted(async () => {
await init(props.data);
});
watch(props, (newValue: any) => {
if (newValue.visible) {
init(newValue.data);
watch(visible, (newValue: any) => {
if (newValue) {
init(props.data);
}
});
@@ -230,7 +232,11 @@ const getDbNamesWithoutRestore = async () => {
const btnOk = async () => {
restoreForm.value.validate(async (valid: any) => {
if (valid) {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
if (state.restoreMode == 'point-in-time') {
state.form.dbBackupId = 0;
state.form.dbBackupHistoryId = 0;
@@ -238,30 +244,28 @@ const btnOk = async () => {
} else {
state.form.pointInTime = '0001-01-01T00:00:00Z';
}
state.form.repeated = false;
const reqForm = { ...state.form };
let api = dbApi.createDbRestore;
if (props.data) {
api = dbApi.saveDbRestore;
}
api.request(reqForm).then(() => {
try {
state.btnLoading = true;
await api.request(reqForm);
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
ElMessage.error('请正确填写信息');
return false;
} finally {
state.btnLoading = false;
}
});
};
const cancel = () => {
emit('update:visible', false);
visible.value = false;
emit('cancel');
};

View File

@@ -9,11 +9,7 @@
:searchItems="searchItems"
:before-query-fn="beforeQueryFn"
v-model:query-form="query"
:data="state.data"
:columns="columns"
:total="state.total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
>
<template #dbSelect>
<el-select v-model="query.dbName" placeholder="请选择数据库" style="width: 200px" filterable clearable>

View File

@@ -396,7 +396,7 @@ const removeDeafultExpandId = (id: any) => {
<style lang="scss">
.tag-tree-list {
.tag-tree-data {
height: calc(100vh - 200px);
height: calc(100vh - 202px);
.el-tree-node__content {
height: 40px;

View File

@@ -82,18 +82,14 @@ import { ElMessage } from 'element-plus';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { SearchItem } from '@/components/SearchForm';
import { useVModel } from '@vueuse/core';
import { ResourceTypeEnum, RoleStatusEnum } from '../enums';
const props = defineProps({
visible: {
type: Boolean,
},
account: Object,
});
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const emit = defineEmits(['cancel', 'val-change']);
const relatedColumns = [
TableColumn.new('roleName', '角色名'),
@@ -148,7 +144,7 @@ let relatedRoleIds: Number[] = []; // 用户已关联的角色ids
const { releateQuery, unRelatedQuery, showResourceDialog } = toRefs(state);
const dialogVisible = useVModel(props, 'visible', emit);
const dialogVisible = defineModel<boolean>('visible', { default: false });
const searchAccountRoles = async () => {
state.releateQuery.id = props.account?.id;

View File

@@ -8,7 +8,7 @@
filterable
placeholder="请输入账号模糊搜索并选择"
v-bind="$attrs"
:ref="(el: any) => focus && el?.focus()"
:ref="(el: any) => props.focus && el?.focus()"
>
<el-option v-for="item in accounts" :key="item.id" :label="`${item.username} [${item.name}]`" :value="item.id"> </el-option>
</el-select>
@@ -18,12 +18,8 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { accountApi } from '../../api';
import { useVModel } from '@vueuse/core';
const props = defineProps({
modelValue: {
type: Object,
},
// 是否获取焦点
focus: {
type: Boolean,
@@ -31,9 +27,7 @@ const props = defineProps({
},
});
const emit = defineEmits(['update:modelValue']);
const accountId = useVModel(props, 'modelValue', emit);
const accountId = defineModel('modelValue');
const accounts: any = ref([]);

View File

@@ -408,7 +408,7 @@ const info = async (data: any) => {
}
.tree-data {
height: calc(100vh - 200px);
height: calc(100vh - 202px);
}
}

View File

@@ -43,18 +43,12 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { SearchItem } from '@/components/SearchForm';
import { useVModel } from '@vueuse/core';
import AccountSelectFormItem from '../account/components/AccountSelectFormItem.vue';
const props = defineProps({
visible: {
type: Boolean,
},
role: Object,
});
const emit = defineEmits(['update:visible', 'cancel', 'val-change']);
const perms = {
saveAccountRole: 'account:saveRoles',
};
@@ -94,7 +88,7 @@ const state = reactive({
const { query, addAccountDialog } = toRefs(state);
const dialogVisible = useVModel(props, 'visible', emit);
const dialogVisible = defineModel('visible', { default: false });
onMounted(() => {
if (Object.keys(actionBtns).length > 0) {

View File

@@ -7,6 +7,11 @@
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563"
integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==
"@babel/parser@^7.23.6":
version "7.23.6"
resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b"
integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==
"@babel/runtime@^7.21.0":
version "7.21.5"
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
@@ -450,13 +455,14 @@
estree-walker "^2.0.2"
source-map-js "^1.0.2"
"@vue/compiler-core@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.12.tgz#3346c0f55ce0d59e17c21d9eef9154b70c19931b"
integrity sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w==
"@vue/compiler-core@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.0.tgz#2a35bc4b70afb5504ff62f916e22f9080d8149b6"
integrity sha512-cw4S15PkNGTKkP9OFFl4wnQoJJk+HqaYBafgrpDnSukiQGpcYJeRpzmqnCVCIkl6V6Eqsv58E0OAdl6b592vuA==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/shared" "3.3.12"
"@babel/parser" "^7.23.6"
"@vue/shared" "3.4.0"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.0.2"
@@ -468,25 +474,24 @@
"@vue/compiler-core" "3.3.11"
"@vue/shared" "3.3.11"
"@vue/compiler-dom@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.12.tgz#267c54b388d58f30fc120ea496ebf27d4ea8368b"
integrity sha512-RdJU9oEYaoPKUdGXCy0l+i4clesdDeLmbvRlszoc9iagsnBnMmQtYfCPVQ5BHB6o7K4SCucDdJM2Dh3oXB0D6g==
"@vue/compiler-dom@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.0.tgz#25d4c1e24a2e37c4136cdc593cd533d371bc573b"
integrity sha512-E957uOhpoE48YjZGWeAoLmNYd3UeU4oIP8kJi8Rcsb9l2tV8Z48Jn07Zgq1aW0v3vuhlmydEKkKKbhLpADHXEA==
dependencies:
"@vue/compiler-core" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/compiler-core" "3.4.0"
"@vue/shared" "3.4.0"
"@vue/compiler-sfc@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.12.tgz#6ec2c19858f264671457699c1f3a0a6fedf429fe"
integrity sha512-yy5b9e7b79dsGbMmglCe/YnhCQgBkHO7Uf6JfjWPSf2/5XH+MKn18LhzhHyxbHdJgnA4lZCqtXzLaJz8Pd8lMw==
"@vue/compiler-sfc@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.0.tgz#dab7b87a9a09e3860a4b5c9baf6b7d3ab8af0c0f"
integrity sha512-PWE0mE2yW7bJS7PmaCrVDEG6KPaDJo0pb4AKnCxJ5lRRDO4IwL/fswBGhCpov+v/c+N/e+hQHpXNwvqU9BtUXg==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/compiler-core" "3.3.12"
"@vue/compiler-dom" "3.3.12"
"@vue/compiler-ssr" "3.3.12"
"@vue/reactivity-transform" "3.3.12"
"@vue/shared" "3.3.12"
"@babel/parser" "^7.23.6"
"@vue/compiler-core" "3.4.0"
"@vue/compiler-dom" "3.4.0"
"@vue/compiler-ssr" "3.4.0"
"@vue/shared" "3.4.0"
estree-walker "^2.0.2"
magic-string "^0.30.5"
postcss "^8.4.32"
@@ -516,13 +521,13 @@
"@vue/compiler-dom" "3.3.11"
"@vue/shared" "3.3.11"
"@vue/compiler-ssr@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.12.tgz#e62499c6003ccd09acb7190167d08845e3a0eaa5"
integrity sha512-adCiMJPznfWcQyk/9HSuXGja859IaMV+b8UNSVzDatqv7h0PvT9BEeS22+gjkWofDiSg5d78/ZLls3sLA+cn3A==
"@vue/compiler-ssr@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.0.tgz#0e437c6e619ce4ec515d2d77a0a9e67e877cc2cd"
integrity sha512-+oXKy105g9DIYQKDi3Gwung0xqQX5gJHr0GR+Vf7yK/WkNDM6q61ummcKmKAB85EIst8y3vj2PA9z9YU5Oc4DQ==
dependencies:
"@vue/compiler-dom" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/compiler-dom" "3.4.0"
"@vue/shared" "3.4.0"
"@vue/devtools-api@^6.5.0":
version "6.5.0"
@@ -540,58 +545,47 @@
estree-walker "^2.0.2"
magic-string "^0.30.5"
"@vue/reactivity-transform@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.12.tgz#4cb871b597eb8b321577b4d7f1e93eaebca16128"
integrity sha512-g5TijmML7FyKkLt6QnpqNmA4KD7K/T5SbXa88Bhq+hydNQEkzA8veVXWAQuNqg9rjaFYD0rPf0a9NofKA0ENgg==
"@vue/reactivity@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.4.0.tgz#84e1d4196bed3fb471e3593d187680a35cfee23a"
integrity sha512-X6BvQjNcgKKHWPQzlRJjZvIu72Kkn8xJSv6VNptqWh8dToMknD0Hch1l4N7llKgVt6Diq4lMeUnErbZFvuGlAA==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/compiler-core" "3.3.12"
"@vue/shared" "3.3.12"
estree-walker "^2.0.2"
magic-string "^0.30.5"
"@vue/shared" "3.4.0"
"@vue/reactivity@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.12.tgz#b4a62a7678ab20c1ef32f991342ddbb8532417da"
integrity sha512-vOJORzO8DlIx88cgTnMLIf2GlLYpoXAKsuoQsK6SGdaqODjxO129pVPTd2s/N/Mb6KKZEFIHIEwWGmtN4YPs+g==
"@vue/runtime-core@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.4.0.tgz#6c49cac6142d941954c68c2888160c8fbcdbfc11"
integrity sha512-NYrj/JgMMqnSWcIud8lLzDQrBLu+EVEeQ56QE9DYJeKG2eFrnQy8o/h57R9nCprafHs0uImKL3xsdXjHseYVxw==
dependencies:
"@vue/shared" "3.3.12"
"@vue/reactivity" "3.4.0"
"@vue/shared" "3.4.0"
"@vue/runtime-core@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.12.tgz#67ee6cfc2e85d656946975239ea635ec42dde5f6"
integrity sha512-5iL4w7MZrSGKEZU2wFAYhDZdZmgn+s//73EfgDXW1M+ZUOl36md7tlWp1QFK/ladiq4FvQ82shVjo0KiPDPr0A==
"@vue/runtime-dom@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.4.0.tgz#9397aa9e74c6f44a03c4f4755d931a10dbf6ec44"
integrity sha512-1ZoHEsA5l77qbx2F+SWo/hQdBksPuOmww1t/jznidDG+xMB/iidafEFvo2ZTtZii0JfTIrlDhjshfYUvQC17wQ==
dependencies:
"@vue/reactivity" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/runtime-dom@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.12.tgz#28a239496e589037774cba7c1b27057242eedb11"
integrity sha512-8mMzqiIdl+IYa/OXwKwk6/4ebLq7cYV1pUcwCSwBK2KerUa6cwGosen5xrCL9f8o2DJ9TfPFwbPEvH7OXzUpoA==
dependencies:
"@vue/runtime-core" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/runtime-core" "3.4.0"
"@vue/shared" "3.4.0"
csstype "^3.1.3"
"@vue/server-renderer@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.12.tgz#f0246aba5d5d6fdfa840ac9e4f32d76f03b20665"
integrity sha512-OZ0IEK5TU5GXb5J8/wSplyxvGGdIcwEmS8EIO302Vz8K6fGSgSJTU54X0Sb6PaefzZdiN3vHsLXO8XIeF8crQQ==
"@vue/server-renderer@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.4.0.tgz#7b0c5e71463140c64d05c314f46a07c9dcc62625"
integrity sha512-GuOVCyLDlWPu8nKo5AUxb8B+iB/Ik4I1WwqAlBqf5+y48z6D6rvKshp7KR3cJea+pte1tdTsb0+Ja82KizMZOw==
dependencies:
"@vue/compiler-ssr" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/compiler-ssr" "3.4.0"
"@vue/shared" "3.4.0"
"@vue/shared@3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.11.tgz#f6a038e15237edefcc90dbfe7edb806dd355c7bd"
integrity sha512-u2G8ZQ9IhMWTMXaWqZycnK4UthG1fA238CD+DP4Dm4WJi5hdUKKLg0RMRaRpDPNMdkTwIDkp7WtD0Rd9BH9fLw==
"@vue/shared@3.3.12":
version "3.3.12"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.12.tgz#7c030c4e2f1db8beb638b159cbb86d0ff78c3198"
integrity sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==
"@vue/shared@3.4.0":
version "3.4.0"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.0.tgz#b6e804a4742929f94f0159aafd0a2940621cd8de"
integrity sha512-Nhh3ed3G1R6HDAWiG6YYFt0Zmq/To6u5vjzwa9TIquGheCXPY6nEdIAO8ZdlwXsWqC2yNLj700FOvShpYt5CEA==
"@vueuse/core@^10.7.0":
version "10.7.0"
@@ -944,6 +938,11 @@ element-plus@^2.4.4:
memoize-one "^6.0.0"
normalize-wheel-es "^1.2.0"
entities@^4.5.0:
version "4.5.0"
resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
esbuild@^0.19.3:
version "0.19.8"
resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.19.8.tgz#ad05b72281d84483fa6b5345bd246c27a207b8f1"
@@ -2003,16 +2002,16 @@ vue-router@^4.2.5:
dependencies:
"@vue/devtools-api" "^6.5.0"
vue@^3.3.12:
version "3.3.12"
resolved "https://registry.npmmirror.com/vue/-/vue-3.3.12.tgz#4a3a39e79d22e9826ae7c058863316333c838b63"
integrity sha512-jYNv2QmET2OTHsFzfWHMnqgCfqL4zfo97QwofdET+GBRCHhSCHuMTTvNIgeSn0/xF3JRT5OGah6MDwUFN7MPlg==
vue@^3.4.0:
version "3.4.0"
resolved "https://registry.npmmirror.com/vue/-/vue-3.4.0.tgz#0671a2607265c16ab56041174744b6bed8ac2baa"
integrity sha512-iTE9Ve/7DO/H39+gXHrNkRdnh1jDwPe/fap4brbPKkp1APMkS03OiZ+UY0dwpqtRX0iPWQTkh8Fu3hKgLtaxfA==
dependencies:
"@vue/compiler-dom" "3.3.12"
"@vue/compiler-sfc" "3.3.12"
"@vue/runtime-dom" "3.3.12"
"@vue/server-renderer" "3.3.12"
"@vue/shared" "3.3.12"
"@vue/compiler-dom" "3.4.0"
"@vue/compiler-sfc" "3.4.0"
"@vue/runtime-dom" "3.4.0"
"@vue/server-renderer" "3.4.0"
"@vue/shared" "3.4.0"
which@^2.0.1:
version "2.0.2"

6
server/.gitignore vendored
View File

@@ -2,9 +2,3 @@ static/static
config.yml
mayfly_rsa
mayfly_rsa.pub
# 数据库备份目录
backup/
# MySQL 可执行文件 (mysql mysqldump mysqlbinlog) 所在目录
mysqlutil/
# MariaDB 可执行文件 (mysql mysqldump mysqlbinlog) 所在目录
mariadbutil/

View File

@@ -15,6 +15,7 @@ require (
github.com/go-playground/validator/v10 v10.14.0
github.com/go-sql-driver/mysql v1.7.1
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.1
github.com/kanzihuang/vitess/go/vt/sqlparser v0.0.0-20231018071450-ac8d9f0167e9
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230712084735-068dc2aee82d
@@ -28,17 +29,13 @@ require (
go.mongodb.org/mongo-driver v1.13.1 // mongo
golang.org/x/crypto v0.17.0 // ssh
golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.1.0
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.5.2
gorm.io/gorm v1.25.5
)
require (
github.com/google/uuid v1.3.0
golang.org/x/sync v0.1.0
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect

View File

@@ -61,6 +61,7 @@ func (d *DbBackup) Create(rc *req.Ctx) {
Enabled: true,
Repeated: form.Repeated,
DbInstanceId: db.InstanceId,
LastTime: form.StartTime,
}
tasks = append(tasks, task)
}
@@ -78,6 +79,7 @@ func (d *DbBackup) Save(rc *req.Ctx) {
Name: form.Name,
StartTime: form.StartTime,
Interval: form.Interval,
LastTime: form.StartTime,
}
task.Id = form.Id
biz.ErrIsNilAppendErr(d.DbBackupApp.Save(rc.MetaCtx, task), "保存数据库备份任务失败: %v")

View File

@@ -1,10 +1,14 @@
package config
import sysapp "mayfly-go/internal/sys/application"
import (
sysapp "mayfly-go/internal/sys/application"
)
const (
ConfigKeyDbSaveQuerySQL string = "DbSaveQuerySQL" // 数据库是否记录查询相关sql
ConfigKeyDbQueryMaxCount string = "DbQueryMaxCount" // 数据库查询的最大数量
ConfigKeyDbBackupRestore string = "DbBackupRestore" // 数据库备份
ConfigKeyDbMysqlBin string = "MysqlBin" // mysql可执行文件配置
)
// 获取数据库最大查询数量配置
@@ -16,3 +20,65 @@ func GetDbQueryMaxCount() int {
func GetDbSaveQuerySql() bool {
return sysapp.GetConfigApp().GetConfig(ConfigKeyDbSaveQuerySQL).BoolValue(false)
}
type DbBackupRestore struct {
BackupPath string // 备份文件路径呢
}
// 获取数据库备份配置
func GetDbBackupRestore() *DbBackupRestore {
c := sysapp.GetConfigApp().GetConfig(ConfigKeyDbBackupRestore)
jm := c.GetJsonMap()
dbrc := new(DbBackupRestore)
backupPath := jm["backupPath"]
if backupPath == "" {
backupPath = "./db/backup"
}
dbrc.BackupPath = backupPath
return dbrc
}
// mysql客户端可执行文件配置
type MysqlBin struct {
Path string // 可执行文件路径
MysqlPath string // mysql可执行文件路径
MysqldumpPath string // mysqldump可执行文件路径
MysqlbinlogPath string // mysqlbinlog可执行文件路径
}
// 获取数据库备份配置
func GetMysqlBin() *MysqlBin {
c := sysapp.GetConfigApp().GetConfig(ConfigKeyDbMysqlBin)
jm := c.GetJsonMap()
mbc := new(MysqlBin)
path := jm["path"]
if path == "" {
path = "./db/backup"
}
mbc.Path = path
mysqlPath := jm["mysql"]
if mysqlPath == "" {
mysqlPath = path + "mysql"
}
mbc.MysqlPath = mysqlPath
mysqldumpPath := jm["mysqldump"]
if mysqldumpPath == "" {
mysqldumpPath = path + "mysqldump"
}
mbc.MysqldumpPath = mysqldumpPath
mysqlbinlogPath := jm["mysqlbinlog"]
if mysqlbinlogPath == "" {
mysqlbinlogPath = path + "mysqlbinlog"
}
mbc.MysqlbinlogPath = mysqlbinlogPath
return mbc
}

View File

@@ -0,0 +1 @@
package dbm

View File

@@ -29,6 +29,7 @@ func NewDbBinlog(history *DbBackupHistory) *DbBinlog {
Enabled: true,
Interval: BinlogDownloadInterval,
DbInstanceId: history.DbInstanceId,
LastTime: time.Now(),
}
binlogTask.Id = binlogTask.DbInstanceId
return binlogTask

View File

@@ -4,12 +4,13 @@ import (
"context"
"encoding/binary"
"fmt"
"github.com/google/uuid"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
"mayfly-go/internal/db/domain/service"
"mayfly-go/pkg/model"
"time"
"github.com/google/uuid"
)
var _ service.DbBackupSvc = (*DbBackupSvcImpl)(nil)

View File

@@ -18,11 +18,12 @@ import (
"github.com/pkg/errors"
"golang.org/x/sync/singleflight"
"mayfly-go/internal/db/config"
"mayfly-go/internal/db/dbm"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
"mayfly-go/internal/db/domain/service"
"mayfly-go/pkg/config"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/structx"
)
@@ -145,13 +146,13 @@ func (svc *DbInstanceSvcImpl) Backup(ctx context.Context, backupHistory *entity.
}
cmd := exec.CommandContext(ctx, mysqldumpPath(), args...)
logx.Debug("backup database using mysqldump binary: ", cmd.String())
logx.Debugf("backup database using mysqldump binary: %s", cmd.String())
if err := runCmd(cmd); err != nil {
logx.Errorf("运行 mysqldump 程序失败: %v", err)
return nil, errors.Wrap(err, "运行 mysqldump 程序失败")
}
logx.Debug("Checking dumped file stat", tmpFile)
logx.Debugf("Checking dumped file stat: %s", tmpFile)
if _, err := os.Stat(tmpFile); err != nil {
logx.Errorf("未找到备份文件: %v", err)
return nil, errors.Wrapf(err, "未找到备份文件")
@@ -297,20 +298,20 @@ func parseLocalBinlogFirstEventTime(ctx context.Context, filePath string) (event
// getBinlogDir gets the binlogDir.
func getBinlogDir(instanceId uint64) string {
return filepath.Join(
config.Conf.Db.BackupPath,
config.GetDbBackupRestore().BackupPath,
fmt.Sprintf("instance-%d", instanceId),
"binlog")
}
func getDbInstanceBackupRoot(instanceId uint64) string {
return filepath.Join(
config.Conf.Db.BackupPath,
config.GetDbBackupRestore().BackupPath,
fmt.Sprintf("instance-%d", instanceId))
}
func getDbBackupDir(instanceId, backupId uint64) string {
return filepath.Join(
config.Conf.Db.BackupPath,
config.GetDbBackupRestore().BackupPath,
fmt.Sprintf("instance-%d", instanceId),
fmt.Sprintf("backup-%d", backupId))
}
@@ -885,13 +886,13 @@ func formatDateTime(t time.Time) string {
}
func mysqlPath() string {
return config.Conf.Db.MysqlUtil.Mysql
return config.GetMysqlBin().MysqlPath
}
func mysqldumpPath() string {
return config.Conf.Db.MysqlUtil.MysqlDump
return config.GetMysqlBin().MysqldumpPath
}
func mysqlbinlogPath() string {
return config.Conf.Db.MysqlUtil.MysqlBinlog
return config.GetMysqlBin().MysqlbinlogPath
}

View File

@@ -55,7 +55,6 @@ type Config struct {
Sqlite Sqlite `yaml:"sqlite"`
Redis Redis `yaml:"redis"`
Log Log `yaml:"log"`
Db Db `yaml:"db"`
}
func (c *Config) IfBlankDefaultValue() {
@@ -73,7 +72,6 @@ func (c *Config) IfBlankDefaultValue() {
c.Jwt.Default()
c.Mysql.Default()
c.Sqlite.Default()
c.Db.Default()
}
// 配置文件内容校验
@@ -82,7 +80,7 @@ func (c *Config) Valid() {
c.Aes.Valid()
}
// 替换系统环境变量,如果环境变量中存在该值,则优使用环境变量设定的值
// 替换系统环境变量,如果环境变量中存在该值,则优使用环境变量设定的值
func (c *Config) ReplaceOsEnv() {
serverPort := os.Getenv("MAYFLY_SERVER_PORT")
if serverPort != "" {

View File

@@ -1,13 +0,0 @@
package config
type Db struct {
BackupPath string `yaml:"backup-path"`
MysqlUtil MysqlUtil `yaml:"mysqlutil-path"`
MariadbUtil MysqlUtil `yaml:"mariadbutil-path"`
}
type MysqlUtil struct {
Mysql string `yaml:"mysql"`
MysqlDump string `yaml:"mysqldump"`
MysqlBinlog string `yaml:"mysqlbinlog"`
}

View File

@@ -1,28 +0,0 @@
package config
func (db *Db) Default() {
if len(db.BackupPath) == 0 {
db.BackupPath = "./backup"
}
if len(db.MysqlUtil.Mysql) == 0 {
db.MysqlUtil.Mysql = "./mysqlutil/bin/mysql"
}
if len(db.MysqlUtil.MysqlDump) == 0 {
db.MysqlUtil.MysqlDump = "./mysqlutil/bin/mysqldump"
}
if len(db.MysqlUtil.Mysql) == 0 {
db.MysqlUtil.MysqlBinlog = "./mysqlutil/bin/mysqlbinlog"
}
if len(db.MariadbUtil.Mysql) == 0 {
db.MariadbUtil.Mysql = "./mariadbutil/bin/mariadb"
}
if len(db.MariadbUtil.MysqlDump) == 0 {
db.MariadbUtil.MysqlDump = "./mariadbutil/bin/mariadb-dump"
}
if len(db.MariadbUtil.MysqlBinlog) == 0 {
db.MariadbUtil.MysqlBinlog = "./mariadbutil/bin/mariadb-binlog"
}
}

View File

@@ -1,27 +0,0 @@
package config
func (db *Db) Default() {
if len(db.BackupPath) == 0 {
db.BackupPath = "./backup"
}
if len(db.MysqlUtil.Mysql) == 0 {
db.MysqlUtil.Mysql = "./mysqlutil/bin/mysql"
}
if len(db.MysqlUtil.MysqlDump) == 0 {
db.MysqlUtil.MysqlDump = "./mysqlutil/bin/mysqldump"
}
if len(db.MysqlUtil.MysqlBinlog) == 0 {
db.MysqlUtil.MysqlBinlog = "./mysqlutil/bin/mysqlbinlog"
}
if len(db.MariadbUtil.Mysql) == 0 {
db.MariadbUtil.Mysql = "./mariadbutil/bin/mariadb"
}
if len(db.MariadbUtil.MysqlDump) == 0 {
db.MariadbUtil.MysqlDump = "./mariadbutil/bin/mariadb-dump"
}
if len(db.MariadbUtil.MysqlBinlog) == 0 {
db.MariadbUtil.MysqlBinlog = "./mariadbutil/bin/mariadb-binlog"
}
}

View File

@@ -1,27 +0,0 @@
package config
func (db *Db) Default() {
if len(db.BackupPath) == 0 {
db.BackupPath = "./backup"
}
if len(db.MysqlUtil.Mysql) == 0 {
db.MysqlUtil.Mysql = "./mysqlutil/bin/mysql.exe"
}
if len(db.MysqlUtil.MysqlDump) == 0 {
db.MysqlUtil.MysqlDump = "./mysqlutil/bin/mysqldump.exe"
}
if len(db.MysqlUtil.Mysql) == 0 {
db.MysqlUtil.MysqlBinlog = "./mysqlutil/bin/mysqlbinlog.exe"
}
if len(db.MariadbUtil.Mysql) == 0 {
db.MariadbUtil.Mysql = "./mariadbutil/bin/mariadb.exe"
}
if len(db.MariadbUtil.MysqlDump) == 0 {
db.MariadbUtil.MysqlDump = "./mariadbutil/bin/mariadb-dump.exe"
}
if len(db.MariadbUtil.MysqlBinlog) == 0 {
db.MariadbUtil.MysqlBinlog = "./mariadbutil/bin/mariadb-binlog.exe"
}
}

View File

@@ -0,0 +1,57 @@
package model
import (
"database/sql/driver"
"fmt"
"mayfly-go/pkg/utils/timex"
"strings"
"time"
)
type JsonTime struct {
time.Time
}
func NewJsonTime(t time.Time) JsonTime {
return JsonTime{
Time: t,
}
}
func NowJsonTime() JsonTime {
return JsonTime{
Time: time.Now(),
}
}
func (j JsonTime) MarshalJSON() ([]byte, error) {
var stamp = fmt.Sprintf("\"%s\"", j.Format(timex.DefaultDateTimeFormat))
return []byte(stamp), nil
}
func (j *JsonTime) UnmarshalJSON(b []byte) error {
s := strings.ReplaceAll(string(b), "\"", "")
t, err := time.Parse(timex.DefaultDateTimeFormat, s)
if err != nil {
return err
}
*j = NewJsonTime(t)
return nil
}
func (j JsonTime) Value() (driver.Value, error) {
var zeroTime time.Time
if j.Time.UnixNano() == zeroTime.UnixNano() {
return nil, nil
}
return j.Time, nil
}
func (j *JsonTime) Scan(v interface{}) error {
value, ok := v.(time.Time)
if ok {
*j = JsonTime{Time: value}
return nil
}
return fmt.Errorf("can not convert %v to timestamp", v)
}

View File

@@ -2,6 +2,8 @@ package timex
import "time"
const DefaultDateTimeFormat = "2006-01-02 15:04:05"
func DefaultFormat(time time.Time) string {
return time.Format("2006-01-02 15:04:05")
return time.Format(DefaultDateTimeFormat)
}