feat: redis支持工单流程审批

This commit is contained in:
meilin.huang
2024-03-02 19:08:19 +08:00
parent 76475e807e
commit 49d3f988c9
93 changed files with 1107 additions and 1014 deletions

View File

@@ -17,7 +17,7 @@
"countup.js": "^2.8.0",
"cropperjs": "^1.6.1",
"echarts": "^5.5.0",
"element-plus": "^2.5.6",
"element-plus": "^2.6.0",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",

View File

@@ -74,7 +74,7 @@
trigger="click"
>
<div v-for="(item, index) in tableColumns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
<el-checkbox v-model="item.show" :label="item.label" :true-value="true" :false-value="false" />
</div>
<template #reference>
<el-button icon="Operation" circle :size="props.size"></el-button>

View File

@@ -48,9 +48,6 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
}
let paramsValue = unref(params);
if (api.beforeHandler) {
paramsValue = api.beforeHandler(paramsValue);
}
let apiUrl = url;
// 简单判断该url是否是restful风格
@@ -58,6 +55,10 @@ export function useApiFetch<T>(api: Api, params: any = null, reqOptions: Request
apiUrl = templateResolve(apiUrl, paramsValue);
}
if (api.beforeHandler) {
paramsValue = api.beforeHandler(paramsValue);
}
if (paramsValue) {
const method = options.method?.toLowerCase();
// post和put使用json格式传参

View File

@@ -13,7 +13,10 @@
<enum-tag :enums="FlowBizType" :value="procinst.bizType"></enum-tag>
</el-descriptions-item>
<el-descriptions-item label="发起人">{{ procinst.creator }}</el-descriptions-item>
<el-descriptions-item label="发起人">
<AccountInfo :account-id="procinst.creatorId" :username="procinst.creator" />
<!-- {{ procinst.creator }} -->
</el-descriptions-item>
<el-descriptions-item label="发起时间">{{ dateFormat(procinst.createTime) }}</el-descriptions-item>
<div v-if="procinst.duration">
@@ -41,7 +44,14 @@
<div>
<el-divider content-position="left">业务信息</el-divider>
<component v-if="procinst.bizType" ref="keyValueRef" :is="bizComponents[procinst.bizType]" :biz-key="procinst.bizKey"> </component>
<component
v-if="procinst.bizType"
ref="keyValueRef"
:is="bizComponents[procinst.bizType]"
:biz-key="procinst.bizKey"
:biz-form="procinst.bizForm"
>
</component>
</div>
<div v-if="props.instTaskId">
@@ -80,8 +90,10 @@ import { dateFormat } from '@/common/utils/date';
import ProcdefTasks from './components/ProcdefTasks.vue';
import { formatTime } from '@/common/utils/format';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import AccountInfo from '@/views/system/account/components/AccountInfo.vue';
const DbSqlExecBiz = defineAsyncComponent(() => import('./flowbiz/DbSqlExecBiz.vue'));
const RedisRunWriteCmdBiz = defineAsyncComponent(() => import('./flowbiz/RedisRunWriteCmdBiz.vue'));
const props = defineProps({
procinstId: {
@@ -104,6 +116,7 @@ const emit = defineEmits(['cancel', 'val-change']);
// 业务组件
const bizComponents = shallowReactive({
db_sql_exec_flow: DbSqlExecBiz,
redis_run_write_cmd_flow: RedisRunWriteCmdBiz,
});
const state = reactive({

View File

@@ -52,10 +52,10 @@ import { formatTime } from '@/common/utils/format';
const searchItems = [SearchItem.select('status', '流程状态').withEnum(ProcinstStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
const columns = [
TableColumn.new('procdefName', '流程名'),
TableColumn.new('bizType', '业务').typeTag(FlowBizType),
TableColumn.new('remark', '备注'),
TableColumn.new('creator', '发起人'),
TableColumn.new('procdefName', '流程名'),
TableColumn.new('status', '流程状态').typeTag(ProcinstStatus),
TableColumn.new('bizStatus', '业务状态').typeTag(ProcinstBizStatus),
TableColumn.new('createTime', '发起时间').isTime(),

View File

@@ -41,12 +41,12 @@ import { formatTime } from '@/common/utils/format';
const searchItems = [SearchItem.select('status', '任务状态').withEnum(ProcinstTaskStatus), SearchItem.select('bizType', '业务类型').withEnum(FlowBizType)];
const columns = [
TableColumn.new('procinst.procdefName', '流程名'),
TableColumn.new('procinst.bizType', '业务').typeTag(FlowBizType),
TableColumn.new('procinst.remark', '备注'),
TableColumn.new('procinst.creator', '发起人'),
TableColumn.new('procinst.status', '流程状态').typeTag(ProcinstStatus),
TableColumn.new('status', '任务状态').typeTag(ProcinstTaskStatus),
TableColumn.new('procinst.procdefName', '流程名'),
TableColumn.new('taskName', '当前节点'),
TableColumn.new('procinst.createTime', '发起时间').isTime(),
TableColumn.new('createTime', '开始时间').isTime(),

View File

@@ -30,4 +30,5 @@ export const ProcinstTaskStatus = {
export const FlowBizType = {
DbSqlExec: EnumValue.of('db_sql_exec_flow', 'DBMS-执行SQL'),
RedisRunWriteCmd: EnumValue.of('redis_run_write_cmd_flow', 'Redis-执行write命令'),
};

View File

@@ -0,0 +1,80 @@
<template>
<div>
<el-descriptions :column="3" border>
<el-descriptions-item :span="1" label="名称">{{ redis?.name }}</el-descriptions-item>
<el-descriptions-item :span="1" label="id">{{ redis?.id }}</el-descriptions-item>
<el-descriptions-item :span="1" label="用户名">{{ redis?.username }}</el-descriptions-item>
<el-descriptions-item :span="3" label="关联标签"><ResourceTags :tags="redis.tags" /></el-descriptions-item>
<el-descriptions-item :span="1" label="主机">{{ `${redis?.host}` }}</el-descriptions-item>
<el-descriptions-item :span="1" label="库">{{ state.db }}</el-descriptions-item>
<el-descriptions-item :span="1" label="mode">
{{ redis.mode }}
</el-descriptions-item>
<el-descriptions-item :span="3" label="执行Cmd">
<el-input type="textarea" disabled v-model="cmd" rows="5" />
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script lang="ts" setup>
import { toRefs, reactive, watch, onMounted } from 'vue';
import ResourceTags from '@/views/ops/component/ResourceTags.vue';
import { redisApi } from '@/views/ops/redis/api';
const props = defineProps({
// 业务表单
bizForm: {
type: [String],
default: '',
},
});
const state = reactive({
cmd: '',
db: 0,
redis: {} as any,
});
const { cmd, redis } = toRefs(state);
onMounted(() => {
parseRunCmdForm(props.bizForm);
});
watch(
() => props.bizForm,
(newValue: any) => {
parseRunCmdForm(newValue);
}
);
const parseRunCmdForm = async (bizForm: string) => {
if (!bizForm) {
return;
}
const form = JSON.parse(bizForm);
const cmds = form.cmd.map((item: any, index: number) => {
if (index === 0) {
return item; // 第一个元素直接返回原值
}
if (typeof item === 'string') {
return `'${item}'`; // 字符串加单引号
}
return item; // 其他类型直接返回
});
state.cmd = cmds.join(' ');
state.db = form.db;
const res = await redisApi.redisList.request({ id: form.id });
if (!res.list) {
return;
}
state.redis = res.list?.[0];
};
</script>
<style lang="scss"></style>

View File

@@ -183,6 +183,8 @@
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data?.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="工单流程key">{{ infoDialog.data?.flowProcdefKey }}</el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data?.createTime) }} </el-descriptions-item>
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data?.creator }}</el-descriptions-item>

View File

@@ -1,4 +1,4 @@
import { h, render, VNode } from 'vue';
import { h, render } from 'vue';
import SqlExecDialog from './SqlExecDialog.vue';
export type SqlExecProps = {
@@ -11,27 +11,21 @@ export type SqlExecProps = {
cancelCallback?: Function;
};
const boxId = 'sql-exec-dialog-id';
let boxInstance: VNode;
const SqlExecBox = (props: SqlExecProps): void => {
if (!boxInstance) {
const container = document.createElement('div');
container.id = boxId;
// 创建 虚拟dom
boxInstance = h(SqlExecDialog);
// 将虚拟dom渲染到 container dom 上
render(boxInstance, container);
// 最后将 container 追加到 body 上
document.body.appendChild(container);
}
const boxVue = boxInstance.component;
if (boxVue) {
// 调用open方法显示弹框注意不能使用boxVue.ctx来调用组件函数build打包后ctx会获取不到
boxVue.exposed?.open(props);
}
const propsCancelFn = props.cancelCallback;
// 包装取消回调函数,新增销毁组件代码
props.cancelCallback = () => {
propsCancelFn && propsCancelFn();
setTimeout(() => {
// 销毁组件
render(null, document.body);
}, 500);
};
const vnode = h(SqlExecDialog, {
...props,
visible: true,
});
render(vnode, document.body);
};
export default SqlExecBox;

View File

@@ -1,12 +1,12 @@
<template>
<div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
<monaco-editor height="300px" class="codesql" language="sql" v-model="sqlValue" />
<el-input @keyup.enter="runSql" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<div v-if="state.flowProcdefKey">
<div v-if="props.flowProcdefKey">
<el-divider content-position="left">审批节点</el-divider>
<procdef-tasks :procdef-key="state.flowProcdefKey" />
<procdef-tasks :procdef-key="props.flowProcdefKey" />
</div>
<template #footer>
@@ -20,7 +20,7 @@
</template>
<script lang="ts" setup>
import { toRefs, ref, nextTick, reactive } from 'vue';
import { toRefs, ref, reactive, onMounted } from 'vue';
import { dbApi } from '@/views/ops/db/api';
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance, ElDivider } from 'element-plus';
// import base style
@@ -30,39 +30,24 @@ import { format as sqlFormatter } from 'sql-formatter';
import { SqlExecProps } from './SqlExecBox';
import ProcdefTasks from '@/views/flow/components/ProcdefTasks.vue';
const props = defineProps({
visible: {
type: Boolean,
},
dbId: {
type: [Number],
},
db: {
type: String,
},
sql: {
type: String,
},
});
const props = withDefaults(defineProps<SqlExecProps>(), {});
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,
sqlValue: '',
dbId: 0,
db: '',
flowProcdefKey: '' as any,
remark: '',
btnLoading: false,
});
const { dialogVisible, sqlValue, remark, btnLoading } = toRefs(state);
state.sqlValue = props.sql as any;
let runSuccessCallback: any;
let cancelCallback: any;
let runSuccess: boolean = false;
onMounted(() => {
open();
});
/**
* 执行sql
*/
@@ -75,14 +60,14 @@ const runSql = async () => {
try {
state.btnLoading = true;
const res = await dbApi.sqlExec.request({
id: state.dbId,
db: state.db,
id: props.dbId,
db: props.db,
remark: state.remark,
sql: state.sqlValue.trim(),
});
// 存在流程审批
if (state.flowProcdefKey) {
if (props.flowProcdefKey) {
runSuccess = false;
ElMessage.success('工单提交成功');
return;
@@ -101,10 +86,9 @@ const runSql = async () => {
runSuccess = false;
} finally {
if (runSuccess) {
if (runSuccessCallback) {
runSuccessCallback();
if (props.runSuccessCallback) {
props.runSuccessCallback();
}
// cancel();
}
state.btnLoading = false;
cancel();
@@ -113,34 +97,20 @@ const runSql = async () => {
const cancel = () => {
state.dialogVisible = false;
// 没有执行成功,并且取消回调函数存在,则执行
if (!runSuccess && cancelCallback) {
cancelCallback();
}
props.cancelCallback && props.cancelCallback();
setTimeout(() => {
state.dbId = 0;
state.sqlValue = '';
state.remark = '';
runSuccessCallback = null;
cancelCallback = null;
runSuccess = false;
}, 200);
};
const open = (props: SqlExecProps) => {
runSuccessCallback = props.runSuccessCallback;
cancelCallback = props.cancelCallback;
props.dbType = props.dbType || 'mysql';
state.sqlValue = sqlFormatter(props.sql, { language: props.dbType });
state.dbId = props.dbId;
state.db = props.db;
state.flowProcdefKey = props.flowProcdefKey;
const open = () => {
state.sqlValue = sqlFormatter(props.sql, { language: props.dbType || 'mysql' });
state.dialogVisible = true;
nextTick(() => {
setTimeout(() => {
remarkInputRef.value?.focus();
});
});
setTimeout(() => {
remarkInputRef.value?.focus();
}, 200);
};
defineExpose({ open });

View File

@@ -742,7 +742,6 @@ const onExitEditMode = (rowData: any, column: any, rowIndex = 0) => {
};
const submitUpdateFields = async () => {
debugger;
const dbInst = getNowDbInst();
if (cellUpdateMap.size == 0) {
return;

View File

@@ -17,8 +17,8 @@
<el-checkbox
v-model="item.show"
:label="`${!item.columnComment ? item.columnName : item.columnName + ' [' + item.columnComment + ']'}`"
:true-label="true"
:false-label="false"
:true-value="true"
:false-value="false"
size="small"
/>
</div>
@@ -55,7 +55,7 @@
title="展示配置"
trigger="click"
>
<el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-label="true" :false-label="false" size="small" />
<el-checkbox v-model="dbConfig.showColumnComment" label="显示字段备注" :true-value="true" :false-value="false" size="small" />
<template #reference>
<el-link type="primary" icon="setting" :underline="false"></el-link>
</template>

View File

@@ -57,7 +57,7 @@
<el-tab-pane label="其他配置" name="other">
<el-form-item prop="enableRecorder" label="终端回放">
<el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
<el-checkbox v-model="form.enableRecorder" :true-value="1" :false-value="-1"></el-checkbox>
</el-form-item>
<el-form-item prop="sshTunnelMachineId" label="SSH隧道">

View File

@@ -141,7 +141,7 @@
<div class="key-detail card pd5">
<el-tabs @tab-remove="removeDataTab" v-model="state.activeName">
<el-tab-pane closable v-for="dt in state.dataTabs" :key="dt.key" :label="dt.label" :name="dt.key">
<key-detail :redisId="scanParam.id" :db="scanParam.db" :key-info="dt.keyInfo" @change-key="searchKey()" @del-key="delKey" />
<key-detail :redis="redisInst" :key-info="dt.keyInfo" @change-key="searchKey()" @del-key="delKey" />
</el-tab-pane>
</el-tabs>
</div>
@@ -178,7 +178,7 @@
<script lang="ts" setup>
import { redisApi } from './api';
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick } from 'vue';
import { ref, defineAsyncComponent, toRefs, reactive, onMounted, nextTick, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { isTrue, notBlank, notNull } from '@/common/assert';
import { copyToClipboard } from '@/common/utils/string';
@@ -189,6 +189,7 @@ import { Contextmenu, ContextmenuItem } from '@/components/contextmenu';
import { sleep } from '@/common/utils/loading';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { Splitpanes, Pane } from 'splitpanes';
import { RedisInst } from './redis';
const KeyDetail = defineAsyncComponent(() => import('./KeyDetail.vue'));
@@ -240,6 +241,7 @@ const NodeTypeRedis = new NodeType(RedisNodeType.Redis).withLoadNodesFunc(async
return new TagTreeNode(x, `db${x}`, NodeTypeDb).withIsLeaf(true).withParams({
id: redisInfo.id,
db: x,
flowProcdefKey: redisInfo.flowProcdefKey,
name: `db${x}`,
keys: 0,
});
@@ -269,6 +271,11 @@ const NodeTypeDb = new NodeType(RedisNodeType.Db).withNodeClickFunc((nodeData: T
resetScanParam();
state.scanParam.id = nodeData.params.id;
state.scanParam.db = nodeData.params.db;
redisInst.value.id = nodeData.params.id;
redisInst.value.db = Number.parseInt(nodeData.params.db);
redisInst.value.flowProcdefKey = nodeData.params.flowProcdefKey;
scan();
});
@@ -281,6 +288,7 @@ const treeProps = {
const defaultCount = 250;
const keyTreeRef: any = ref(null);
const redisInst: Ref<RedisInst> = ref(new RedisInst());
const state = reactive({
tags: [],
@@ -506,15 +514,11 @@ const flushDb = () => {
type: 'warning',
})
.then(() => {
redisApi.flushDb
.request({
id: state.scanParam.id,
db: state.scanParam.db,
})
.then(() => {
ElMessage.success('清除成功!');
searchKey();
});
// FLUSHDB [ASYNC | SYNC]
redisInst.value.runCmd(['FLUSHDB']).then(() => {
ElMessage.success('清除成功!');
searchKey();
});
})
.catch(() => {});
};
@@ -526,19 +530,9 @@ const cancelNewKey = () => {
const newKey = async () => {
const keyInfo = state.newKeyDialog.keyInfo;
const keyType = keyInfo.type;
const key = keyInfo.key;
notBlank(key, '键名不能为空');
if (keyType == 'string') {
await redisApi.setString.request({
id: state.scanParam.id,
db: state.scanParam.db,
key: key,
value: '',
});
}
showKeyDetail(
{
...keyInfo,
@@ -565,11 +559,8 @@ const delKey = (key: string) => {
type: 'warning',
})
.then(async () => {
await redisApi.delKey.request({
key,
id: state.scanParam.id,
db: state.scanParam.db,
});
// DEL key [key ...]
await redisInst.value.runCmd(['DEL', key]);
ElMessage.success('删除成功!');
searchKey();

View File

@@ -4,8 +4,7 @@
<!-- key info -->
<key-header
ref="keyHeader"
:redis-id="redisId"
:db="db"
:redis="props.redis"
:key-info="state.keyInfo"
@refresh-content="refreshContent"
@del-key="delKey"
@@ -15,7 +14,7 @@
</key-header>
<!-- key content -->
<component ref="keyValueRef" :is="components[componentName]" :redis-id="redisId" :db="db" :key-info="keyInfo"> </component>
<component ref="keyValueRef" :is="components[componentName]" :redis="props.redis" :key-info="keyInfo"> </component>
</el-container>
</div>
</template>
@@ -23,6 +22,7 @@
import { defineAsyncComponent, watch, ref, shallowReactive, reactive, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import KeyHeader from './KeyHeader.vue';
import { RedisInst } from './redis';
const KeyValueString = defineAsyncComponent(() => import('./KeyValueString.vue'));
const KeyValueHash = defineAsyncComponent(() => import('./KeyValueHash.vue'));
@@ -41,11 +41,9 @@ const components = shallowReactive({
const keyValueRef = ref(null) as any;
const props = defineProps({
redisId: {
type: Number,
},
db: {
type: Number,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -55,7 +53,6 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'changeKey', 'delKey']);
const state = reactive({
redisId: 0,
keyInfo: {} as any,
});

View File

@@ -47,13 +47,12 @@ import { reactive, watch, toRefs, onMounted } from 'vue';
import { redisApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { formatTime } from '@/common/utils/format';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: Number,
},
db: {
type: Number,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -63,7 +62,6 @@ const props = defineProps({
const emit = defineEmits(['refreshContent', 'delKey', 'changeKey']);
const state = reactive({
redisId: 0,
keyInfo: {
key: '',
type: '',
@@ -85,8 +83,8 @@ onMounted(() => {
const refreshKey = async () => {
const ttl = await redisApi.keyTtl.request({
id: props.redisId,
db: props.db,
id: props.redis.id,
db: props.redis.db,
key: state.oldKey,
});
state.keyInfo.timed = ttl;
@@ -101,12 +99,8 @@ const renameKey = async () => {
if (!state.oldKey || state.ki.key == state.oldKey) {
return;
}
await redisApi.renameKey.request({
id: props.redisId,
db: props.db,
newKey: state.ki.key,
key: state.oldKey,
});
// RENAME key newkey
await props.redis.runCmd(['RENAME', state.oldKey, state.ki.key]);
ElMessage.success('设置成功');
emit('changeKey');
};
@@ -131,22 +125,15 @@ const ttlKey = async () => {
return;
}
await redisApi.expireKey.request({
id: props.redisId,
db: props.db,
key: state.ki.key,
seconds: state.ki.timed,
});
// EXPIRE key seconds [NX | XX | GT | LT]
await props.redis.runCmd(['EXPIRE', state.ki.key, state.ki.timed]);
ElMessage.success('设置成功');
emit('changeKey');
};
const persistKey = async () => {
await redisApi.persistKey.request({
id: props.redisId,
db: props.db,
key: state.keyInfo.key,
});
// PERSIST key
await props.redis.runCmd(['PERSIST', state.keyInfo.key]);
ElMessage.success('设置成功');
emit('changeKey');
};

View File

@@ -52,21 +52,15 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import FormatViewer from './FormatViewer.vue';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: [Number],
require: true,
default: 0,
},
db: {
type: [Number],
require: true,
default: 0,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -76,8 +70,6 @@ const props = defineProps({
const formatViewerRef = ref(null) as any;
const state = reactive({
redisId: 0,
db: 0,
key: '',
scanParam: {
cursor: 0,
@@ -98,8 +90,6 @@ const state = reactive({
const { hashValues, total, loadMoreDisable, editDialog } = toRefs(state);
onMounted(() => {
state.redisId = props.redisId;
state.db = props.db;
state.key = props.keyInfo?.key;
initData();
});
@@ -118,16 +108,15 @@ const hscan = async (resetTableData = false, resetCursor = false) => {
state.scanParam.cursor = 0;
}
const scanRes = await redisApi.hscan.request({
...getBaseReqParam(),
match: getScanMatch(),
...state.scanParam,
});
state.scanParam.cursor = scanRes.cursor;
state.loadMoreDisable = scanRes.cursor == 0;
state.total = scanRes.keySize;
props.redis.runCmd(['HLEN', state.key]).then((res) => (state.total = res));
// HSCAN key cursor [MATCH pattern] [COUNT count]
// 返回值 [coursor, keys:[]]
let scanRes = await props.redis.runCmd(['HSCAN', state.key, state.scanParam.cursor, 'MATCH', getScanMatch(), 'COUNT', state.scanParam.count]);
state.scanParam.cursor = scanRes[0];
state.loadMoreDisable = state.scanParam.cursor == 0;
const keys = scanRes[1];
const keys = scanRes.keys;
const hashValue = [];
const fieldCount = keys.length / 2;
let nextFieldIndex = 0;
@@ -143,10 +132,7 @@ const hscan = async (resetTableData = false, resetCursor = false) => {
};
const hdel = async (field: any, index: any) => {
await redisApi.hdel.request({
...getBaseReqParam(),
field,
});
await props.redis.runCmd(['HDEL', state.key, field]);
ElMessage.success('删除成功');
state.hashValues.splice(index, 1);
@@ -161,57 +147,25 @@ const showEditDialog = (row: any) => {
};
const confirmEditData = async () => {
const param = getBaseReqParam();
const field = state.editDialog.field;
notBlank(field, 'field不能为空');
// 存在数据行,则说明为修改,则要先删除旧数据后新增
const dataRow = state.editDialog.dataRow;
if (dataRow) {
await redisApi.hdel.request({
...param,
field: dataRow.field,
});
}
// 获取hash value内容并新增
const value = formatViewerRef.value.getContent();
const res = await redisApi.hset.request({
...param,
value: [
{
field,
value: value,
},
],
});
const res = await props.redis.runCmd(['HSET', state.key, field, value]);
ElMessage.success('保存成功');
if (dataRow) {
state.editDialog.dataRow.value = value;
state.editDialog.dataRow.field = field;
// 响应0则为被覆盖则重新scan
if (res == 0) {
hscan(true, true);
} else {
// 响应0则为被覆盖则重新scan
if (res == 0) {
hscan(true, true);
} else {
state.hashValues.unshift({ value, field });
state.total++;
}
state.hashValues.unshift({ value, field });
state.total++;
}
state.editDialog.visible = false;
state.editDialog.dataRow = null;
};
const getBaseReqParam = () => {
return {
id: state.redisId,
db: state.db,
key: state.key,
};
};
defineExpose({ initData });
</script>
<style lang="scss">

View File

@@ -1,12 +1,12 @@
<template>
<div>
<el-button @click="showEditDialog(null)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-button @click="showEditDialog(null, -1)" icon="plus" size="small" plain type="primary" class="mb10">添加新行</el-button>
<el-table size="small" border :data="values" height="450" min-height="300" stripe>
<el-table-column type="index" :label="'ID (Total: ' + total + ')'" sortable width="100"> </el-table-column>
<el-table-column resizable sortable prop="value" label="value" show-overflow-tooltip min-width="200"> </el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-link @click="showEditDialog(scope.row)" :underline="false" type="primary" icon="edit" plain></el-link>
<el-link @click="showEditDialog(scope.row, scope.$index)" :underline="false" type="primary" icon="edit" plain></el-link>
<el-popconfirm title="确定删除?" @confirm="lrem(scope.row, scope.$index)">
<template #reference>
<el-link v-auth="'redis:data:del'" :underline="false" type="danger" icon="delete" size="small" plain class="ml5"></el-link>
@@ -38,20 +38,14 @@
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs, onMounted } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import FormatViewer from './FormatViewer.vue';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: [Number],
require: true,
default: 0,
},
db: {
type: [Number],
require: true,
default: 0,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -61,8 +55,6 @@ const props = defineProps({
const formatViewerRef = ref(null) as any;
const state = reactive({
redisId: 0,
db: 0,
key: '',
pageNum: 1,
pageSize: 50,
@@ -70,17 +62,15 @@ const state = reactive({
values: [] as any,
loadMoreDisable: false,
editDialog: {
index: -1,
visible: false,
content: '',
dataRow: null as any,
},
});
const { total, values, loadMoreDisable, editDialog } = toRefs(state);
onMounted(() => {
state.redisId = props.redisId;
state.db = props.db;
state.key = props.keyInfo?.key;
initData();
});
@@ -93,14 +83,12 @@ const initData = () => {
const getListValue = async (resetTableData = false) => {
const pageNum = state.pageNum;
const pageSize = state.pageSize;
const res = await redisApi.getListValue.request({
...getBaseReqParam(),
start: (pageNum - 1) * pageSize,
stop: pageNum * pageSize - 1,
});
state.total = res.len;
const datas = res.list.map((x: any) => {
props.redis.runCmd(['LLEN', state.key]).then((res) => (state.total = res));
// LRANGE key start stop
const res = await props.redis.runCmd(['LRANGE', state.key, (pageNum - 1) * pageSize, pageNum * pageSize - 1]);
const datas = res.map((x: any) => {
return {
value: x,
};
@@ -114,71 +102,41 @@ const getListValue = async (resetTableData = false) => {
state.loadMoreDisable = state.values.length === state.total;
};
// const lset = async (row: any, rowIndex: number) => {
// await redisApi.setListValue.request({
// ...getBaseReqParam(),
// index: (state.pageNum - 1) * state.pageSize + rowIndex,
// value: row.value,
// });
// ElMessage.success('数据保存成功');
// };
const showEditDialog = (row: any) => {
state.editDialog.dataRow = row;
const showEditDialog = (row: any, index = -1) => {
state.editDialog.index = index;
state.editDialog.content = row ? row.value : '';
state.editDialog.visible = true;
};
const confirmEditData = async () => {
const param = getBaseReqParam();
// 存在数据行,则说明为修改,则要先删除旧数据后新增
const dataRow = state.editDialog.dataRow;
if (dataRow) {
await redisApi.lrem.request({
member: state.editDialog.dataRow.value,
count: 1,
...param,
});
}
const index = state.editDialog.index;
// 获取list member内容并新增
const member = formatViewerRef.value.getContent();
await redisApi.saveListValue.request({
value: [member],
...param,
});
try {
// 索引=-1 说明是新增
if (index == -1) {
// RPUSH key element [element ...]
await props.redis.runCmd(['RPUSH', state.key, member]);
} else {
// LSET key index element
await props.redis.runCmd(['LSET', state.key, index, member]);
}
ElMessage.success('保存成功');
if (dataRow) {
state.editDialog.dataRow.value = member;
} else {
state.values.push({ value: member });
state.total++;
ElMessage.success('保存成功');
initData();
} finally {
state.editDialog.visible = false;
}
state.editDialog.visible = false;
state.editDialog.dataRow = null;
};
const lrem = async (row: any, index: any) => {
await redisApi.lrem.request({
...getBaseReqParam(),
member: row.value,
count: 1,
});
// LREM key count element
await props.redis.runCmd(['LREM', state.key, 1, row.value]);
ElMessage.success('删除成功');
state.values.splice(index, 1);
state.total--;
};
const getBaseReqParam = () => {
return {
id: state.redisId,
db: state.db,
key: state.key,
};
};
defineExpose({ initData });
</script>
<style lang="scss"></style>

View File

@@ -48,20 +48,14 @@
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs, onMounted } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import FormatViewer from './FormatViewer.vue';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: [Number],
require: true,
default: 0,
},
db: {
type: [Number],
require: true,
default: 0,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -71,8 +65,6 @@ const props = defineProps({
const formatViewerRef = ref(null) as any;
const state = reactive({
redisId: 0,
db: 0,
key: '',
filterValue: '',
@@ -94,8 +86,6 @@ const state = reactive({
const { total, setDatas, loadMoreDisable, editDialog } = toRefs(state);
onMounted(() => {
state.redisId = props.redisId;
state.db = props.db;
state.key = props.keyInfo?.key;
initData();
});
@@ -114,26 +104,24 @@ const sscanData = async (resetDatas = true, resetCursor = false) => {
if (resetCursor) {
state.scanParam.cursor = 0;
}
const res = await redisApi.sscan.request({
...getBaseReqParam(),
match: getScanMatch(),
...state.scanParam,
});
// SSCAN key cursor [MATCH pattern] [COUNT count]
// 响应[cursor, vals[]]
const res = await props.redis.runCmd(['SSCAN', state.key, state.scanParam.cursor, 'MATCH', getScanMatch(), 'COUNT', state.scanParam.count]);
if (resetDatas) {
state.setDatas = [];
}
res.keys.forEach((x: any) => {
res[1].forEach((x: any) => {
state.setDatas.push({
value: x,
});
});
state.scanParam.cursor = res.cursor;
state.loadMoreDisable = res.cursor == 0;
state.scanParam.cursor = res[0];
state.loadMoreDisable = state.scanParam.cursor == 0;
};
const getTotal = () => {
redisApi.scard.request(getBaseReqParam()).then((res) => {
// SCARD key
props.redis.runCmd(['SCARD', state.key]).then((res) => {
state.total = res;
});
};
@@ -145,23 +133,16 @@ const showEditDialog = (row: any) => {
};
const confirmEditData = async () => {
const param = getBaseReqParam();
// 存在数据行,则说明为修改,则要先删除旧数据后新增
const dataRow = state.editDialog.dataRow;
if (dataRow) {
await redisApi.srem.request({
member: state.editDialog.dataRow.value,
...param,
});
await props.redis.runCmd(['SREM', state.key, state.editDialog.dataRow.value]);
}
// 获取set member内容并新增
const member = formatViewerRef.value.getContent();
await redisApi.sadd.request({
member,
...param,
});
// SADD key member [member ...]
await props.redis.runCmd(['SADD', state.key, member]);
ElMessage.success('保存成功');
if (dataRow) {
@@ -175,23 +156,13 @@ const confirmEditData = async () => {
};
const srem = async (row: any, index: any) => {
await redisApi.srem.request({
...getBaseReqParam(),
member: row.value,
});
// SREM key member [member ...]
await props.redis.runCmd(['SREM', state.key, row.value]);
ElMessage.success('删除成功');
state.setDatas.splice(index, 1);
state.total--;
};
const getBaseReqParam = () => {
return {
id: state.redisId,
db: state.db,
key: state.key,
};
};
defineExpose({ initData });
</script>
<style lang="scss"></style>

View File

@@ -12,21 +12,15 @@
</template>
<script lang="ts" setup>
import { ref, watch, reactive, toRefs, onMounted } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { notEmpty } from '@/common/assert';
import FormatViewer from './FormatViewer.vue';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: [Number],
require: true,
default: 0,
},
db: {
type: [Number],
require: true,
default: 0,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -36,8 +30,6 @@ const props = defineProps({
const formatViewerRef = ref(null) as any;
const state = reactive({
redisId: 0,
db: 0,
key: '',
keyInfo: {
key: '',
@@ -61,8 +53,6 @@ watch(props, (newVal) => {
});
const setProps = (val: any) => {
state.redisId = val.redisId;
state.db = val.db;
state.key = val.keyInfo?.key;
initData();
};
@@ -73,7 +63,7 @@ const initData = () => {
const getStringValue = async () => {
if (state.key) {
state.string.value = await redisApi.getString.request(getBaseReqParam());
state.string.value = await props.redis.runCmd(['GET', state.key]);
}
};
@@ -81,21 +71,10 @@ const saveValue = async () => {
state.string.value = formatViewerRef.value.getContent();
notEmpty(state.string.value, 'value不能为空');
await redisApi.setString.request({
...getBaseReqParam(),
value: state.string.value,
});
await props.redis.runCmd(['SET', state.key, state.string.value]);
ElMessage.success('数据保存成功');
};
const getBaseReqParam = () => {
return {
id: state.redisId,
db: state.db,
key: state.key,
};
};
defineExpose({ initData });
</script>
<style lang="scss">

View File

@@ -52,20 +52,14 @@
</template>
<script lang="ts" setup>
import { ref, reactive, toRefs, onMounted } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import FormatViewer from './FormatViewer.vue';
import { RedisInst } from './redis';
const props = defineProps({
redisId: {
type: [Number],
require: true,
default: 0,
},
db: {
type: [Number],
require: true,
default: 0,
redis: {
type: RedisInst,
required: true,
},
keyInfo: {
type: [Object],
@@ -75,8 +69,6 @@ const props = defineProps({
const formatViewerRef = ref(null) as any;
const state = reactive({
redisId: 0,
db: 0,
key: '',
filterValue: '',
scanCursor: 0,
@@ -96,8 +88,6 @@ const state = reactive({
const { total, values, loadMoreDisable, editDialog } = toRefs(state);
onMounted(() => {
state.redisId = props.redisId;
state.db = props.db;
state.key = props.keyInfo?.key;
initData();
});
@@ -105,7 +95,7 @@ onMounted(() => {
const initData = async () => {
state.pageNum = 1;
state.filterValue = '';
await getTotal();
getTotal();
await zrevrange(true);
};
@@ -120,17 +110,14 @@ const loadDatas = (resetTableData = false) => {
const zrevrange = async (resetTableData = false) => {
const pageNum = state.pageNum;
const pageSize = state.pageSize;
const res = await redisApi.zrevrange.request({
...getBaseReqParam(),
start: (pageNum - 1) * pageSize,
stop: pageNum * pageSize - 1,
});
// ZREVRANGE key start stop [WITHSCORES]
const res = await props.redis.runCmd(['ZREVRANGE', state.key, (pageNum - 1) * pageSize, pageNum * pageSize - 1, 'WITHSCORES']);
const vs = [];
for (let member of res) {
vs.push({
score: member.Score,
value: member.Member,
value: member[0],
score: member[1],
});
}
if (resetTableData) {
@@ -150,14 +137,11 @@ const zscanData = async (resetTableData = true, resetCursor = false) => {
if (resetCursor) {
state.scanCursor = 0;
}
const res = await redisApi.zscan.request({
...getBaseReqParam(),
match: getScanMatch(),
cursor: state.scanCursor,
count: state.pageSize,
});
// ZSCAN key cursor [MATCH pattern] [COUNT count]
// 响应[coursor, vals[]]
const res = await props.redis.runCmd(['ZSCAN', state.key, state.scanCursor, 'MATCH', getScanMatch(), 'COUNT', state.pageSize]);
const keys = res.keys;
const keys = res[1];
const vs = [];
const memCount = keys.length / 2;
let nextMemndex = 0;
@@ -171,20 +155,15 @@ const zscanData = async (resetTableData = true, resetCursor = false) => {
state.values.push(...vs);
}
state.scanCursor = res.cursor;
state.loadMoreDisable = res.cursor == 0;
state.scanCursor = res[0];
state.loadMoreDisable = state.scanCursor == 0;
};
const getTotal = () => {
redisApi.zcard
.request({
id: state.redisId,
db: state.db,
key: state.key,
})
.then((res) => {
state.total = res;
});
// ZCARD key
props.redis.runCmd(['ZCARD', state.key]).then((res) => {
state.total = res;
});
};
const showEditDialog = (row: any) => {
@@ -195,25 +174,18 @@ const showEditDialog = (row: any) => {
};
const confirmEditData = async () => {
const param = getBaseReqParam();
// 存在数据行,则说明为修改,则要先删除旧数据后新增
const dataRow = state.editDialog.dataRow;
if (dataRow) {
await redisApi.zrem.request({
member: state.editDialog.dataRow.value,
...param,
});
// ZREM key member [member ...]
await props.redis.runCmd(['ZREM', state.key, state.editDialog.dataRow.value]);
}
const score = state.editDialog.score;
// 获取zset member内容并新增
const member = formatViewerRef.value.getContent();
await redisApi.zadd.request({
score,
member,
...param,
});
// ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
await props.redis.runCmd(['ZADD', state.key, score, member]);
ElMessage.success('保存成功');
if (dataRow) {
@@ -228,23 +200,12 @@ const confirmEditData = async () => {
};
const zrem = async (row: any, index: any) => {
await redisApi.zrem.request({
...getBaseReqParam(),
member: row.value,
});
await props.redis.runCmd(['ZREM', state.key, row.value]);
ElMessage.success('删除成功');
state.values.splice(index, 1);
state.total--;
};
const getBaseReqParam = () => {
return {
id: state.redisId,
db: state.db,
key: state.key,
};
};
defineExpose({ initData });
</script>
<style lang="scss"></style>

View File

@@ -71,6 +71,8 @@
<el-form-item prop="remark" label="备注">
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
</el-form-item>
<procdef-select-form-item v-model="form.flowProcdefKey" />
</el-tab-pane>
<el-tab-pane label="其他配置" name="other">
@@ -99,6 +101,7 @@ import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
import TagTreeSelect from '../component/TagTreeSelect.vue';
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
import ProcdefSelectFormItem from '@/views/flow/components/ProcdefSelectFormItem.vue';
const props = defineProps({
visible: {
@@ -170,6 +173,7 @@ const state = reactive({
db: '',
remark: '',
sshTunnelMachineId: -1,
flowProcdefKey: '',
},
submitForm: {} as any,
dbList: [0],

View File

@@ -127,7 +127,7 @@
<el-descriptions-item :span="3" label="库">{{ detailDialog.data.db }}</el-descriptions-item>
<el-descriptions-item :span="3" label="备注">{{ detailDialog.data.remark }}</el-descriptions-item>
<el-descriptions-item :span="3" label="工单流程key">{{ detailDialog.data?.flowProcdefKey }}</el-descriptions-item>
<el-descriptions-item :span="3" label="SSH隧道">{{ detailDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }} </el-descriptions-item>
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(detailDialog.data.createTime) }} </el-descriptions-item>

View File

@@ -13,40 +13,125 @@ export const redisApi = {
keyInfo: Api.newGet('/redis/{id}/{db}/key-info'),
keyTtl: Api.newGet('/redis/{id}/{db}/key-ttl'),
keyMemuse: Api.newGet('/redis/{id}/{db}/key-memuse'),
renameKey: Api.newPost('/redis/{id}/{db}/rename-key'),
expireKey: Api.newPost('/redis/{id}/{db}/expire-key'),
persistKey: Api.newDelete('/redis/{id}/{db}/persist-key'),
// 获取权限列表
// 获取key列表
scan: Api.newPost('/redis/{id}/{db}/scan'),
getString: Api.newGet('/redis/{id}/{db}/string-value'),
setString: Api.newPost('/redis/{id}/{db}/string-value'),
getHashValue: Api.newGet('/redis/{id}/{db}/hash-value'),
hscan: Api.newGet('/redis/{id}/{db}/hscan'),
hget: Api.newGet('/redis/{id}/{db}/hget'),
hset: Api.newPost('/redis/{id}/{db}/hset'),
hdel: Api.newDelete('/redis/{id}/{db}/hdel'),
saveHashValue: Api.newPost('/redis/{id}/{db}/hash-value'),
getSetValue: Api.newGet('/redis/{id}/{db}/set-value'),
scard: Api.newGet('/redis/{id}/{db}/scard'),
sscan: Api.newPost('/redis/{id}/{db}/sscan'),
sadd: Api.newPost('/redis/{id}/{db}/sadd'),
srem: Api.newPost('/redis/{id}/{db}/srem'),
saveSetValue: Api.newPost('/redis/{id}/{db}/set-value'),
del: Api.newDelete('/redis/{id}/{db}/scan/{cursor}/{count}'),
delKey: Api.newDelete('/redis/{id}/{db}/key'),
flushDb: Api.newDelete('/redis/{id}/{db}/flushdb'),
lrem: Api.newPost('/redis/{id}/{db}/lrem'),
getListValue: Api.newGet('/redis/{id}/{db}/list-value'),
saveListValue: Api.newPost('/redis/{id}/{db}/list-value'),
setListValue: Api.newPost('/redis/{id}/{db}/list-value/lset'),
zcard: Api.newGet('/redis/{id}/{db}/zcard'),
zscan: Api.newGet('/redis/{id}/{db}/zscan'),
zrevrange: Api.newGet('/redis/{id}/{db}/zrevrange'),
zadd: Api.newPost('/redis/{id}/{db}/zadd'),
zrem: Api.newPost('/redis/{id}/{db}/zrem'),
runCmd: Api.newPost('/redis/{id}/{db}/run-cmd'),
};
export function splitargs(line: string) {
var ret = [] as any;
if (!line || typeof line.length !== 'number') {
return ret;
}
var len = line.length;
var pos = 0;
while (true) {
// skip blanks
while (pos < len && isspace(line[pos])) {
pos += 1;
}
if (pos === len) {
break;
}
var inq = false; // if we are in "quotes"
var insq = false; // if we are in "single quotes"
var done = false;
var current = '';
while (!done) {
var c = line[pos];
if (inq) {
if (c === '\\' && pos + 1 < len) {
pos += 1;
switch (line[pos]) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'a':
c = 'a';
break;
default:
c = line[pos];
break;
}
current += c;
} else if (c === '"') {
// closing quote must be followed by a space or
// nothing at all.
if (pos + 1 < len && !isspace(line[pos + 1])) {
throw new Error("Expect '\"' followed by a space or nothing, got '" + line[pos + 1] + "'.");
}
done = true;
} else if (pos === len) {
throw new Error('Unterminated quotes.');
} else {
current += c;
}
} else if (insq) {
if (c === '\\' && line[pos + 1] === "'") {
pos += 1;
current += "'";
} else if (c === "'") {
// closing quote must be followed by a space or
// nothing at all.
if (pos + 1 < len && !isspace(line[pos + 1])) {
throw new Error('Expect "\'" followed by a space or nothing, got "' + line[pos + 1] + '".');
}
done = true;
} else if (pos === len) {
throw new Error('Unterminated quotes.');
} else {
current += c;
}
} else {
if (pos === len) {
done = true;
} else {
switch (c) {
case ' ':
case '\n':
case '\r':
case '\t':
done = true;
break;
case '"':
inq = true;
break;
case "'":
insq = true;
break;
default:
current += c;
break;
}
}
}
if (pos < len) {
pos += 1;
}
}
ret.push(current);
current = '';
}
return ret;
}
function isspace(ch: string) {
return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r' || ch === '\v' || ch === '\f';
}

View File

@@ -0,0 +1,28 @@
import { h, render } from 'vue';
import CmdExecDialog from './CmdExecDialog.vue';
export type CmdExecProps = {
id: number;
db: number | string;
cmd: any[];
flowProcdefKey?: string;
visible?: boolean;
runSuccessFn?: Function;
cancelFn?: Function;
};
const showCmdExecBox = (props: CmdExecProps): void => {
const propsCancelFn = props.cancelFn;
props.cancelFn = () => {
propsCancelFn && propsCancelFn();
setTimeout(() => {
// 销毁组件
render(null, document.body);
}, 500);
};
const vnode = h(CmdExecDialog, { ...props, visible: true });
render(vnode, document.body);
};
export default showCmdExecBox;

View File

@@ -0,0 +1,95 @@
<template>
<div>
<el-dialog title="待执行cmd" v-model="dialogVisible" :show-close="false" width="600px" @close="cancel">
<el-input type="textarea" disabled v-model="state.cmdStr" class="mt5" rows="5" />
<el-input @keyup.enter="runCmd" ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<div v-if="props.flowProcdefKey">
<el-divider content-position="left">审批节点</el-divider>
<procdef-tasks :procdef-key="props.flowProcdefKey" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button @click="runCmd" type="primary" :loading="btnLoading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { toRefs, ref, reactive, onMounted } from 'vue';
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance, ElDivider } from 'element-plus';
import ProcdefTasks from '@/views/flow/components/ProcdefTasks.vue';
import { redisApi } from '../api';
import { CmdExecProps } from './CmdExecBox';
const props = withDefaults(defineProps<CmdExecProps>(), {});
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,
flowProcdefKey: '' as any,
cmdStr: '',
remark: '',
btnLoading: false,
});
const { dialogVisible, remark, btnLoading } = toRefs(state);
onMounted(() => {
show(props);
});
const show = (props: CmdExecProps) => {
const cmdArr = props.cmd.map((item: any, index: number) => {
if (index === 0) {
return item; // 第一个元素直接返回原值
}
if (typeof item === 'string') {
return `'${item}'`; // 字符串加单引号
}
return item; // 其他类型直接返回
});
state.cmdStr = cmdArr.join(' ');
state.dialogVisible = props.visible || true;
setTimeout(() => {
remarkInputRef.value?.focus();
}, 200);
};
/**
* 执行cmd
*/
const runCmd = async () => {
if (!state.remark) {
ElMessage.error('请输入执行备注信息');
return;
}
try {
state.btnLoading = true;
await redisApi.runCmd.request({
id: props.id,
db: props.db,
cmd: props.cmd,
remark: state.remark,
});
props.runSuccessFn && props.runSuccessFn();
ElMessage.success('工单提交成功');
} finally {
state.btnLoading = false;
cancel();
}
};
const cancel = () => {
state.dialogVisible = false;
props.cancelFn && props.cancelFn();
};
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,138 @@
import { redisApi } from './api';
import showCmdExecBox from './components/CmdExecBox';
export class RedisInst {
/**
* 实例id
*/
id: number;
/**
* db
*/
db: number;
/**
* 流程定义key若存在则需要审批执行
*/
flowProcdefKey: string;
/**
* 执行命令
* @param cmd 命令列表如:['SET', 'key', 'value']
* @returns 执行结果
*/
async runCmd(cmd: any[]) {
// 工单流程定义存在,并且为写入命令时,弹窗输入工单相关信息并提交
if (this.flowProcdefKey && writeCmd[cmd[0].toUpperCase()]) {
showCmdExecBox({
id: this.id,
db: this.db,
flowProcdefKey: this.flowProcdefKey,
cmd,
});
// 报错,阻止后续继续执行
throw new Error('提交工单执行');
}
return await redisApi.runCmd.request({
id: this.id,
db: this.db,
cmd,
});
}
}
const writeCmd = {
APPEND: 'APPEND key value',
BLMOVE: 'BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout',
BLPOP: 'BLPOP key [key ...] timeout',
BRPOP: 'BRPOP key [key ...] timeout',
BRPOPLPUSH: 'BRPOPLPUSH source destination timeout',
BZPOPMAX: 'BZPOPMAX key [key ...] timeout',
BZPOPMIN: 'BZPOPMIN key [key ...] timeout',
COPY: 'COPY source destination [DB destination-db] [REPLACE]',
DECR: 'DECR key',
DECRBY: 'DECRBY key decrement',
DEL: 'DEL key [key ...]',
EVAL: 'EVAL script numkeys key [key ...] arg [arg ...]',
EVALSHA: 'EVALSHA sha1 numkeys key [key ...] arg [arg ...]',
EXPIRE: 'EXPIRE key seconds',
EXPIREAT: 'EXPIREAT key timestamp',
FLUSHALL: 'FLUSHALL',
FLUSHDB: 'FLUSHDB',
GEOADD: 'GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]',
GETDEL: 'GETDEL key',
GETSET: 'GETSET key value',
HDEL: 'HDEL key field [field ...]',
HINCRBY: 'HINCRBY key field increment',
HINCRBYFLOAT: 'HINCRBYFLOAT key field increment',
HMSET: 'HMSET key field value [field value ...]',
HSET: 'HSET key field value',
HSETNX: 'HSETNX key field value',
INCR: 'INCR key',
INCRBY: 'INCRBY key increment',
INCRBYFLOAT: 'INCRBYFLOAT key increment',
LINSERT: 'LINSERT key BEFORE|AFTER pivot value',
LMOVE: 'LMOVE source destination LEFT|RIGHT LEFT|RIGHT',
LPOP: 'LPOP key',
LPUSH: 'LPUSH key value [value ...]',
LPUSHX: 'LPUSHX key value',
LREM: 'LREM key count value',
LSET: 'LSET key index value',
LTRIM: 'LTRIM key start stop',
MIGRATE: 'MIGRATE host port key destination-db timeout',
MOVE: 'MOVE key db',
MSET: 'MSET key value [key value ...]',
MSETNX: 'MSETNX key value [key value ...]',
PERSIST: 'PERSIST key',
PEXPIRE: 'PEXPIRE key milliseconds',
PEXPIREAT: 'PEXPIREAT key milliseconds-timestamp',
PSETEX: 'PSETEX key milliseconds value',
PUBLISH: 'PUBLISH channel message',
RENAME: 'RENAME key newkey',
RENAMENX: 'RENAMENX key newkey',
RESTORE: 'RESTORE key ttl serialized-value',
RPOP: 'RPOP key',
RPOPLPUSH: 'RPOPLPUSH source destination',
RPUSH: 'RPUSH key value [value ...]',
RPUSHX: 'RPUSHX key value',
SADD: 'SADD key member [member ...]',
SCRIPT: ['SCRIPT EXISTS script [script ...]', 'SCRIPT FLUSH', 'SCRIPT KILL', 'SCRIPT LOAD script'],
SDIFFSTORE: 'SDIFFSTORE destination key [key ...]',
SET: 'SET key value',
SETBIT: 'SETBIT key offset value',
SETEX: 'SETEX key seconds value',
SETNX: 'SETNX key value',
SETRANGE: 'SETRANGE key offset value',
SINTERSTORE: 'SINTERSTORE destination key [key ...]',
SMOVE: 'SMOVE source destination member',
SORT: 'SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]',
SPOP: 'SPOP key',
SREM: 'SREM key member [member ...]',
SUNIONSTORE: 'SUNIONSTORE destination key [key ...]',
SWAPDB: 'SWAPDB index1 index2',
UNLINK: 'UNLINK key [key ...]',
XADD: 'XADD key ID field string [field string ...]',
XDEL: 'XDEL key ID [ID ...]',
XGROUP: [
'XGROUP CREATE key groupname id|$ [MKSTREAM]',
'XGROUP CREATECONSUMER key groupname consumername',
'XGROUP DELCONSUMER key groupname consumername',
'XGROUP DESTROY key groupname',
'XGROUP SETID key groupname id|$',
],
XTRIM: 'XTRIM key MAXLEN [~] count',
ZADD: 'ZADD key score member [score] [member]',
ZDIFFSTORE: 'ZDIFFSTORE destination numkeys key [key ...]',
ZINCRBY: 'ZINCRBY key increment member',
ZINTERSTORE: 'ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
ZPOPMAX: 'ZPOPMAX key [count]',
ZPOPMIN: 'ZPOPMIN key [count]',
ZRANGESTORE: 'ZRANGESTORE dst src min max [BYSCORE|BYLEX] [REV] [LIMIT offset count]',
ZREM: 'ZREM key member [member ...]',
ZREMRANGEBYLEX: 'ZREMRANGEBYLEX key min max',
ZREMRANGEBYRANK: 'ZREMRANGEBYRANK key start stop',
ZREMRANGEBYSCORE: 'ZREMRANGEBYSCORE key min max',
ZUNIONSTORE: 'ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]',
};

View File

@@ -27,7 +27,7 @@ export function keysToTree(keys: any, separator: string = ':', openStatus: any =
return formatTreeData(tree, '', separator, openStatus, forceCut);
}
export function keysToList(keys: any, separator: string = ':', openStatus: any = null, forceCut = 20000) {
export function keysToList(keys: any) {
return keys.map((x: string) => {
return {
key: x,

View File

@@ -0,0 +1,54 @@
<template>
<div>
<el-popover v-if="props.accountId" @show="getAccountInfo(props.accountId)" placement="top-start" title="账号信息" :width="400" trigger="click">
<template #reference>
<el-link type="primary">{{ props.username }}</el-link>
</template>
<el-descriptions v-loading="loading" :column="2" border>
<el-descriptions-item label="username">{{ account.username }}</el-descriptions-item>
<el-descriptions-item label="姓名">
{{ account.name }}
</el-descriptions-item>
<el-descriptions-item :span="2" label="角色">
<el-tag v-for="role in account.roles" :key="role.code" class="ml5">
{{ role.roleName }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</el-popover>
<span v-else>{{ props.username }}</span>
</div>
</template>
<script lang="ts" setup>
import { reactive, toRefs } from 'vue';
import { accountApi } from '../../api';
const props = defineProps({
accountId: {
type: [Number],
},
username: {
type: [String],
required: true,
},
});
const state = reactive({
account: {} as any,
loading: false,
});
const { account, loading } = toRefs(state);
const getAccountInfo = async (id: number) => {
try {
state.loading = true;
state.account = await accountApi.getAccountDetail.request({ id });
} finally {
state.loading = false;
}
};
</script>
<style lang="scss"></style>

View File

@@ -25,6 +25,7 @@ export const roleApi = {
export const accountApi = {
list: Api.newGet('/sys/accounts'),
querySimple: Api.newGet('/sys/accounts/simple'),
getAccountDetail: Api.newGet('/sys/accounts/{id}'),
save: Api.newPost('/sys/accounts'),
update: Api.newPut('/sys/accounts/{id}'),
del: Api.newDelete('/sys/accounts/{id}'),

View File

@@ -1,6 +1,10 @@
<template>
<div>
<page-table :page-api="logApi.list" :search-items="searchItems" v-model:query-form="query" :columns="columns"> </page-table>
<page-table :page-api="logApi.list" :search-items="searchItems" v-model:query-form="query" :columns="columns">
<template #creator="{ data }">
<account-info :account-id="data.creatorId" :username="data.creator" />
</template>
</page-table>
</div>
</template>
@@ -11,6 +15,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { LogTypeEnum } from '../enums';
import { OptionsApi, SearchItem } from '@/components/SearchForm';
import AccountInfo from '../account/components/AccountInfo.vue';
const searchItems = [
SearchItem.select('creatorId', '操作人')
@@ -33,7 +38,7 @@ const searchItems = [
];
const columns = [
TableColumn.new('creator', '操作人'),
TableColumn.new('creator', '操作人').isSlot().noShowOverflowTooltip(),
TableColumn.new('createTime', '操作时间').isTime(),
TableColumn.new('type', '结果').typeTag(LogTypeEnum),
TableColumn.new('description', '描述'),

View File

@@ -16,7 +16,7 @@ var (
initFuncs = make([]InitFunc, 0)
)
// 添加初始化ioc函数由各个默认自行添加
// 添加初始化ioc函数由各个默认自行添加(直接init方法中ioc.Register注册不会打印ioc相关日志)
func AddInitIocFunc(initIocFunc InitIocFunc) {
initIocFuncs = append(initIocFuncs, initIocFunc)
}
@@ -37,7 +37,7 @@ func InitOther() {
// 为所有注册的实例注入其依赖的其他组件实例
biz.ErrIsNil(ioc.InjectComponents())
// 调用各个默认的初始化函数
// 调用各个模块的初始化函数
for _, initFunc := range initFuncs {
go initFunc()
}

View File

@@ -1,12 +1,9 @@
package application
import (
"mayfly-go/internal/auth/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(oauth2AppImpl), ioc.WithComponentName("Oauth2App"))
}

View File

@@ -4,6 +4,6 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newAuthAccountRepo(), ioc.WithComponentName("Oauth2AccountRepo"))
}

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/auth/application"
"mayfly-go/internal/auth/infrastructure/persistence"
"mayfly-go/internal/auth/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -124,7 +124,7 @@ func (d *Db) ExecSql(rc *req.Ctx) {
for _, s := range sqls {
s = stringx.TrimSpaceAndBr(s)
// 多条执行,暂不支持查询语句
if isMulti {
if isMulti && len(s) > 10 {
biz.IsTrue(!strings.HasPrefix(strings.ToLower(s[:10]), "select"), "多条语句执行暂不不支持select语句")
}

View File

@@ -2,14 +2,11 @@ package application
import (
"fmt"
"mayfly-go/internal/db/infrastructure/persistence"
"mayfly-go/pkg/ioc"
"sync"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(instanceAppImpl), ioc.WithComponentName("DbInstanceApp"))
ioc.Register(new(dbAppImpl), ioc.WithComponentName("DbApp"))
ioc.Register(new(dbSqlExecAppImpl), ioc.WithComponentName("DbSqlExecApp"))

View File

@@ -97,7 +97,6 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
// 如果配置为0则不校验分页参数
maxCount := config.GetDbQueryMaxCount()
if maxCount != 0 {
if !strings.Contains(lowerSql, "limit") &&
// 兼容oracle rownum分页
!strings.Contains(lowerSql, "rownum") &&
@@ -118,10 +117,11 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
} else {
execRes, execErr = d.doExec(ctx, execSqlReq, dbSqlExecRecord)
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
if execErr != nil {
return nil, execErr
}
d.saveSqlExecLog(isSelect, dbSqlExecRecord)
return execRes, nil
}
@@ -152,10 +152,13 @@ func (d *dbSqlExecAppImpl) Exec(ctx context.Context, execSqlReq *DbSqlExecReq) (
return execRes, nil
}
func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, procinstStatus flowentity.ProcinstStatus, bizKey string) error {
func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowapp.BizHandleParam) error {
bizKey := bizHandleParam.BizKey
procinstStatus := bizHandleParam.ProcinstStatus
logx.Debugf("DbSqlExec FlowBizHandle -> bizKey: %s, procinstStatus: %s", bizKey, flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
// 流程挂起不处理
if procinstStatus == flowentity.ProcinstSuspended {
if procinstStatus == flowentity.ProcinstStatusSuspended {
return nil
}
dbSqlExec := &entity.DbSqlExec{FlowBizKey: bizKey}
@@ -164,7 +167,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, procinstStatus flo
return nil
}
if procinstStatus != flowentity.ProcinstCompleted {
if procinstStatus != flowentity.ProcinstStatusCompleted {
dbSqlExec.Status = entity.DbSqlExecStatusNo
dbSqlExec.Res = fmt.Sprintf("流程%s", flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
return d.dbSqlExecRepo.UpdateById(ctx, dbSqlExec)
@@ -364,7 +367,7 @@ func (d *dbSqlExecAppImpl) doExec(ctx context.Context, execSqlReq *DbSqlExecReq,
if err != nil {
execRes = err.Error()
dbSqlExecRecord.Status = entity.DbSqlExecStatusFail
dbSqlExecRecord.Res = err.Error()
dbSqlExecRecord.Res = execRes
} else {
dbSqlExecRecord.Res = fmt.Sprintf("执行成功,影响条数: %d", rowsAffected)
}

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(NewInstanceRepo(), ioc.WithComponentName("DbInstanceRepo"))
ioc.Register(newDbRepo(), ioc.WithComponentName("DbRepo"))
ioc.Register(newDbSqlRepo(), ioc.WithComponentName("DbSqlRepo"))

View File

@@ -3,11 +3,16 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/infrastructure/persistence"
"mayfly-go/internal/db/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
initialize.AddInitFunc(application.Init)
initialize.AddTerminateFunc(Terminate)

View File

@@ -12,6 +12,7 @@ type ProcinstVO struct {
BizType string `json:"bizType"` // 业务类型
BizKey string `json:"bizKey"` // 业务key
BizForm string `json:"bizForm"` // 业务form
BizStatus int8 `json:"bizStatus"` // 业务状态
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
TaskKey string `json:"taskKey"` // 当前任务key
@@ -21,6 +22,7 @@ type ProcinstVO struct {
Duration int64 `json:"duration"` // 持续时间(开始到结束)
Creator string `json:"creator"`
CreatorId uint64 `json:"creatorId"`
CreateTime *time.Time `json:"createTime"`
UpdateTime *time.Time `json:"updateTime"`

View File

@@ -1,13 +1,10 @@
package application
import (
"mayfly-go/internal/flow/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(procdefAppImpl), ioc.WithComponentName("ProcdefApp"))
ioc.Register(new(procinstAppImpl), ioc.WithComponentName("ProcinstApp"))
}

View File

@@ -7,18 +7,19 @@ import (
"mayfly-go/pkg/logx"
)
// 流程业务处理函数(流程结束后会根据流程业务类型获取该函数进行处理)
// @param procinstStatus 流程实例状态
// @param bizKey 业务key可为业务数据对应的主键
// type FlowBizHandlerFunc func(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
type BizHandleParam struct {
BizType string //业务类型
BizKey string // 业务key
BizForm string // 业务表单信息
ProcinstStatus entity.ProcinstStatus // 业务状态
}
// 业务流程处理器(流程状态变更后会根据流程业务类型获取对应的处理器进行回调处理)
type FlowBizHandler interface {
// 业务流程处理函数
// @param procinstStatus 流程实例状态
// @param bizKey 业务key可为业务数据对应的主键
FlowBizHandle(ctx context.Context, procinstStatus entity.ProcinstStatus, bizKey string) error
// @param bizHandleParam 业务处理信息可获取实例状态、关联业务key等信息
FlowBizHandle(ctx context.Context, bizHandleParam *BizHandleParam) error
}
var (
@@ -32,11 +33,12 @@ func RegisterBizHandler(flowBizType string, handler FlowBizHandler) {
}
// 流程业务处理
func FlowBizHandle(ctx context.Context, flowBizType string, bizKey string, procinstStatus entity.ProcinstStatus) error {
func FlowBizHandle(ctx context.Context, bizHandleParam *BizHandleParam) error {
flowBizType := bizHandleParam.BizType
if handler, ok := handlers[flowBizType]; !ok {
logx.Warnf("flow biz handler not found: bizType=%s", flowBizType)
return errorx.NewBiz("业务流程处理器不存在")
return errorx.NewBiz("业务处理器不存在")
} else {
return handler.FlowBizHandle(ctx, procinstStatus, bizKey)
return handler.FlowBizHandle(ctx, bizHandleParam)
}
}

View File

@@ -5,6 +5,7 @@ type StarProcParam struct {
BizType string // 业务类型
BizKey string // 业务key
Remark string // 备注
BizForm string // 业务表单信息
}
type CompleteProcinstTaskParam struct {

View File

@@ -65,10 +65,10 @@ func (p *procdefAppImpl) DeleteProcdef(ctx context.Context, defId uint64) error
// 判断该流程实例是否可以执行修改操作
func (p *procdefAppImpl) canModify(prodefId uint64) error {
if activeInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstActive}); activeInstCount > 0 {
if activeInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstStatusActive}); activeInstCount > 0 {
return errorx.NewBiz("存在运行中的流程实例,无法操作")
}
if suspInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstSuspended}); suspInstCount > 0 {
if suspInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstStatusSuspended}); suspInstCount > 0 {
return errorx.NewBiz("存在挂起中的流程实例,无法操作")
}
return nil

View File

@@ -68,11 +68,12 @@ func (p *procinstAppImpl) StartProc(ctx context.Context, procdefKey string, reqP
procinst := &entity.Procinst{
BizType: reqParam.BizType,
BizKey: reqParam.BizKey,
BizForm: reqParam.BizForm,
BizStatus: entity.ProcinstBizStatusWait,
ProcdefId: procdef.Id,
ProcdefName: procdef.Name,
Remark: reqParam.Remark,
Status: entity.ProcinstActive,
Status: entity.ProcinstStatusActive,
}
task := p.getNextTask(procdef, "")
@@ -97,7 +98,7 @@ func (p *procinstAppImpl) CancelProc(ctx context.Context, procinstId uint64) err
if procinst.CreatorId != la.Id {
return errorx.NewBiz("只能取消自己创建的流程")
}
procinst.Status = entity.ProcinstCancelled
procinst.Status = entity.ProcinstStatusCancelled
procinst.BizStatus = entity.ProcinstBizStatusNo
procinst.SetEnd()
@@ -130,7 +131,7 @@ func (p *procinstAppImpl) CompleteTask(ctx context.Context, instTaskId uint64, r
// 获取下一实例审批任务
task := p.getNextTask(procdef, instTask.TaskKey)
if task == nil {
procinst.Status = entity.ProcinstCompleted
procinst.Status = entity.ProcinstStatusCompleted
procinst.SetEnd()
} else {
procinst.TaskKey = task.TaskKey
@@ -165,7 +166,7 @@ func (p *procinstAppImpl) RejectTask(ctx context.Context, instTaskId uint64, rem
procinst := new(entity.Procinst)
p.GetById(procinst, instTask.ProcinstId)
// 更新流程实例为终止状态,无法重新提交
procinst.Status = entity.ProcinstTerminated
procinst.Status = entity.ProcinstStatusTerminated
procinst.BizStatus = entity.ProcinstBizStatusNo
procinst.SetEnd()
@@ -192,7 +193,7 @@ func (p *procinstAppImpl) BackTask(ctx context.Context, instTaskId uint64, remar
p.GetById(procinst, instTask.ProcinstId)
// 更新流程实例为挂起状态,等待重新提交
procinst.Status = entity.ProcinstSuspended
procinst.Status = entity.ProcinstStatusSuspended
return p.Tx(ctx, func(ctx context.Context) error {
return p.UpdateById(ctx, procinst)
@@ -219,11 +220,17 @@ func (p *procinstAppImpl) cancelInstTasks(ctx context.Context, procinstId uint64
// 触发流程实例状态改变事件
func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context, procinst *entity.Procinst) error {
err := FlowBizHandle(ctx, procinst.BizType, procinst.BizKey, procinst.Status)
err := FlowBizHandle(ctx, &BizHandleParam{
BizType: procinst.BizType,
BizKey: procinst.BizKey,
BizForm: procinst.BizForm,
ProcinstStatus: procinst.Status,
})
if err != nil {
// 业务处理错误,非完成状态则终止流程
if procinst.Status != entity.ProcinstCompleted {
procinst.Status = entity.ProcinstTerminated
if procinst.Status != entity.ProcinstStatusCompleted {
procinst.Status = entity.ProcinstStatusTerminated
procinst.SetEnd()
p.cancelInstTasks(ctx, procinst.Id, "业务处理失败")
}
@@ -233,7 +240,7 @@ func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context,
}
// 处理成功,并且状态为完成,则更新业务状态为成功
if procinst.Status == entity.ProcinstCompleted {
if procinst.Status == entity.ProcinstStatusCompleted {
procinst.BizStatus = entity.ProcinstBizStatusSuccess
procinst.BizHandleRes = "success"
return p.UpdateById(ctx, procinst)

View File

@@ -15,6 +15,7 @@ type Procinst struct {
BizType string `json:"bizType"` // 业务类型
BizKey string `json:"bizKey"` // 业务key
BizForm string `json:"bizForm"` // 业务表单
BizStatus ProcinstBizStatus `json:"bizStatus"` // 业务状态
BizHandleRes string `json:"bizHandleRes"` // 业务处理结果
TaskKey string `json:"taskKey"` // 当前任务key
@@ -38,19 +39,19 @@ func (a *Procinst) SetEnd() {
type ProcinstStatus int8
const (
ProcinstActive ProcinstStatus = 1 // 流程实例正在执行中,当前有活动任务等待执行或者正在运行的流程节点
ProcinstCompleted ProcinstStatus = 2 // 流程实例已经成功执行完成,没有剩余任务或者等待事件
ProcinstSuspended ProcinstStatus = -1 // 流程实例被挂起,暂停执行,可能被驳回等待修改重新提交
ProcinstTerminated ProcinstStatus = -2 // 流程实例被终止,可能是由于某种原因如被拒绝等导致流程无法正常执行
ProcinstCancelled ProcinstStatus = -3 // 流程实例被取消,通常是用户手动操作取消了流程的执行
ProcinstStatusActive ProcinstStatus = 1 // 流程实例正在执行中,当前有活动任务等待执行或者正在运行的流程节点
ProcinstStatusCompleted ProcinstStatus = 2 // 流程实例已经成功执行完成,没有剩余任务或者等待事件
ProcinstStatusSuspended ProcinstStatus = -1 // 流程实例被挂起,暂停执行,可能被驳回等待修改重新提交
ProcinstStatusTerminated ProcinstStatus = -2 // 流程实例被终止,可能是由于某种原因如被拒绝等导致流程无法正常执行
ProcinstStatusCancelled ProcinstStatus = -3 // 流程实例被取消,通常是用户手动操作取消了流程的执行
)
var ProcinstStatusEnum = enumx.NewEnum[ProcinstStatus]("流程状态").
Add(ProcinstActive, "执行中").
Add(ProcinstCompleted, "完成").
Add(ProcinstSuspended, "挂起").
Add(ProcinstTerminated, "终止").
Add(ProcinstCancelled, "取消")
Add(ProcinstStatusActive, "执行中").
Add(ProcinstStatusCompleted, "完成").
Add(ProcinstStatusSuspended, "挂起").
Add(ProcinstStatusTerminated, "终止").
Add(ProcinstStatusCancelled, "取消")
type ProcinstBizStatus int8

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newProcdefRepo(), ioc.WithComponentName("ProcdefRepo"))
ioc.Register(newProcinstRepo(), ioc.WithComponentName("ProcinstRepo"))
ioc.Register(newProcinstTaskRepo(), ioc.WithComponentName("ProcinstTaskRepo"))

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/flow/application"
"mayfly-go/internal/flow/infrastructure/persistence"
"mayfly-go/internal/flow/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -1,13 +1,10 @@
package application
import (
"mayfly-go/internal/machine/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(machineAppImpl), ioc.WithComponentName("MachineApp"))
ioc.Register(new(machineFileAppImpl), ioc.WithComponentName("MachineFileApp"))
ioc.Register(new(machineScriptAppImpl), ioc.WithComponentName("MachineScriptApp"))

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newMachineRepo(), ioc.WithComponentName("MachineRepo"))
ioc.Register(newMachineFileRepo(), ioc.WithComponentName("MachineFileRepo"))
ioc.Register(newMachineScriptRepo(), ioc.WithComponentName("MachineScriptRepo"))

View File

@@ -6,13 +6,17 @@ import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/infrastructure/persistence"
"mayfly-go/internal/machine/router"
"mayfly-go/pkg/eventbus"
"mayfly-go/pkg/global"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
initialize.AddInitFunc(Init)
}

View File

@@ -70,6 +70,12 @@ func checkClientAvailability(interval time.Duration) {
if cli.Info == nil {
continue
}
if cli.sshClient == nil {
continue
}
if cli.sshClient.Conn == nil {
continue
}
if _, _, err := cli.sshClient.Conn.SendRequest("ping", true, nil); err != nil {
logx.Errorf("machine[%s] cache client is not available: %s", cli.Info.Name, err.Error())
DeleteCli(cli.Info.Id)

View File

@@ -1,12 +1,9 @@
package application
import (
"mayfly-go/internal/mongo/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(mongoAppImpl), ioc.WithComponentName("MongoApp"))
}

View File

@@ -4,6 +4,6 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newMongoRepo(), ioc.WithComponentName("MongoRepo"))
}

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/infrastructure/persistence"
"mayfly-go/internal/mongo/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -1,13 +1,10 @@
package application
import (
"mayfly-go/internal/msg/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(msgAppImpl), ioc.WithComponentName("MsgApp"))
}

View File

@@ -4,6 +4,6 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newMsgRepo(), ioc.WithComponentName("MsgRepo"))
}

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/msg/application"
"mayfly-go/internal/msg/infrastructure/persistence"
"mayfly-go/internal/msg/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -0,0 +1,19 @@
package api
import (
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
)
func (r *Redis) RunCmd(rc *req.Ctx) {
var cmdReq form.RunCmdForm
runCmdParam := req.BindJsonAndCopyTo(rc, &cmdReq, new(application.RunCmdParam))
biz.IsTrue(len(cmdReq.Cmd) > 0, "redis命令不能为空")
rc.ReqParam = cmdReq
res, err := r.RedisApp.RunCmd(rc.MetaCtx, r.getRedisConn(rc), runCmdParam)
biz.ErrIsNil(err)
rc.ResData = res
}

View File

@@ -11,16 +11,7 @@ type Redis struct {
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
TagId []uint64 `binding:"required" json:"tagId"`
Remark string `json:"remark"`
}
type Rename struct {
Key string `binding:"required" json:"key"`
NewKey string `binding:"required" json:"newKey"`
}
type Expire struct {
Key string `binding:"required" json:"key"`
Seconds int64 `binding:"required" json:"seconds"`
FlowProcdefKey string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}
type KeyInfo struct {
@@ -28,33 +19,6 @@ type KeyInfo struct {
Timed int64 `json:"timed"`
}
type StringValue struct {
KeyInfo
Value any `binding:"required" json:"value"`
}
type HashValue struct {
KeyInfo
Value []map[string]any `binding:"required" json:"value"`
}
type SetValue struct {
KeyInfo
Value []any `binding:"required" json:"value"`
}
type ListValue struct {
KeyInfo
Value []any `binding:"required" json:"value"`
}
// list lset命令参数入参
type ListSetValue struct {
Key string `binding:"required" json:"key"`
Index int64 `json:"index"`
Value any `binding:"required" json:"value"`
}
type RedisScanForm struct {
Cursor map[string]uint64 `json:"cursor"`
Match string `json:"match"`
@@ -68,19 +32,9 @@ type ScanForm struct {
Count int64 `json:"count"`
}
type SmemberOption struct {
Key string `json:"key"`
Member any `json:"member"`
}
type LRemOption struct {
Key string `json:"key"`
Count int64 `json:"count"`
Member any `json:"member"`
}
type ZAddOption struct {
Key string `json:"key"`
Score float64 `json:"score"`
Member any `json:"member"`
type RunCmdForm struct {
Id uint64 `json:"id"`
Db int `json:"db"`
Cmd []any `json:"cmd"`
Remark string `json:"remark"`
}

View File

@@ -1,79 +0,0 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"time"
)
func (r *Redis) Hscan(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
count := rc.QueryIntDefault("count", 10)
match := rc.Query("match")
cursor := rc.QueryIntDefault("cursor", 0)
contextTodo := context.TODO()
cmdable := ri.GetCmdable()
keys, nextCursor, err := cmdable.HScan(contextTodo, key, uint64(cursor), match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "hcan err: %s")
keySize, err := cmdable.HLen(contextTodo, key).Result()
biz.ErrIsNilAppendErr(err, "hlen err: %s")
rc.ResData = collx.M{
"keys": keys,
"cursor": nextCursor,
"keySize": keySize,
}
}
func (r *Redis) Hdel(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
field := rc.Query("field")
rc.ReqParam = collx.Kvs("redis", ri.Info, "key", key, "field", field)
delRes, err := ri.GetCmdable().HDel(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hdel err: %s")
rc.ResData = delRes
}
func (r *Redis) Hget(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
field := rc.Query("field")
res, err := ri.GetCmdable().HGet(context.TODO(), key, field).Result()
biz.ErrIsNilAppendErr(err, "hget err: %s")
rc.ResData = res
}
func (r *Redis) Hset(rc *req.Ctx) {
hashValue := req.BindJsonAndValid(rc, new(form.HashValue))
hv := hashValue.Value[0]
ri := r.getRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "hash", hv)
res, err := ri.GetCmdable().HSet(context.TODO(), hashValue.Key, hv["field"].(string), hv["value"]).Result()
biz.ErrIsNilAppendErr(err, "hset失败: %s")
rc.ResData = res
}
func (r *Redis) SaveHashValue(rc *req.Ctx) {
hashValue := req.BindJsonAndValid(rc, new(form.HashValue))
ri := r.getRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "hash", hashValue)
cmd := ri.GetCmdable()
key := hashValue.Key
contextTodo := context.TODO()
for _, v := range hashValue.Value {
res := cmd.HSet(contextTodo, key, v["field"].(string), v["value"])
biz.ErrIsNilAppendErr(res.Err(), "保存hash值失败: %s")
}
if hashValue.Timed != 0 && hashValue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(hashValue.Timed))
}
}

View File

@@ -7,10 +7,8 @@ import (
"mayfly-go/internal/redis/rdm"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strings"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
@@ -133,39 +131,3 @@ func (r *Redis) MemoryUsage(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
rc.ResData = ri.GetCmdable().MemoryUsage(context.Background(), key).Val()
}
func (r *Redis) DeleteKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "key", key)
ri.GetCmdable().Del(context.Background(), key)
}
func (r *Redis) RenameKey(rc *req.Ctx) {
form := req.BindJsonAndValid(rc, new(form.Rename))
ri := r.getRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "rename", form)
ri.GetCmdable().Rename(context.Background(), form.Key, form.NewKey)
}
func (r *Redis) ExpireKey(rc *req.Ctx) {
form := req.BindJsonAndValid(rc, new(form.Expire))
ri := r.getRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "expire", form)
ri.GetCmdable().Expire(context.Background(), form.Key, time.Duration(form.Seconds)*time.Second)
}
// 移除过期时间
func (r *Redis) PersistKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
rc.ReqParam = collx.Kvs("redis", ri.Info, "key", key)
ri.GetCmdable().Persist(context.Background(), key)
}
// 清空库
func (r *Redis) FlushDb(rc *req.Ctx) {
ri := r.getRedisConn(rc)
rc.ReqParam = ri.Info
ri.GetCmdable().FlushDB(context.Background())
}

View File

@@ -1,58 +0,0 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
func (r *Redis) GetListValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
ctx := context.TODO()
cmdable := ri.GetCmdable()
len, err := cmdable.LLen(ctx, key).Result()
biz.ErrIsNilAppendErr(err, "获取list长度失败: %s")
start := rc.QueryIntDefault("start", 0)
stop := rc.QueryIntDefault("stop", 10)
res, err := cmdable.LRange(ctx, key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "获取list值失败: %s")
rc.ResData = collx.M{
"len": len,
"list": res,
}
}
func (r *Redis) Lrem(rc *req.Ctx) {
option := req.BindJsonAndValid(rc, new(form.LRemOption))
cmd := r.getRedisConn(rc).GetCmdable()
res, err := cmd.LRem(context.TODO(), option.Key, int64(option.Count), option.Member).Result()
biz.ErrIsNilAppendErr(err, "lrem失败: %s")
rc.ResData = res
}
func (r *Redis) SaveListValue(rc *req.Ctx) {
listValue := req.BindJsonAndValid(rc, new(form.ListValue))
cmd := r.getRedisConn(rc).GetCmdable()
key := listValue.Key
ctx := context.TODO()
for _, v := range listValue.Value {
cmd.RPush(ctx, key, v)
}
}
func (r *Redis) Lset(rc *req.Ctx) {
listSetValue := req.BindJsonAndValid(rc, new(form.ListSetValue))
ri := r.getRedisConn(rc)
_, err := ri.GetCmdable().LSet(context.TODO(), listSetValue.Key, listSetValue.Index, listSetValue.Value).Result()
biz.ErrIsNilAppendErr(err, "list set失败: %s")
}

View File

@@ -1,71 +0,0 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"time"
)
func (r *Redis) GetSetValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
res, err := ri.GetCmdable().SMembers(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取set值失败: %s")
rc.ResData = res
}
func (r *Redis) SaveSetValue(rc *req.Ctx) {
keyvalue := req.BindJsonAndValid(rc, new(form.SetValue))
cmd := r.getRedisConn(rc).GetCmdable()
key := keyvalue.Key
// 简单处理->先删除,后新增
cmd.Del(context.TODO(), key)
cmd.SAdd(context.TODO(), key, keyvalue.Value...)
if keyvalue.Timed != -1 {
cmd.Expire(context.TODO(), key, time.Second*time.Duration(keyvalue.Timed))
}
}
func (r *Redis) Scard(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
total, err := ri.GetCmdable().SCard(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "scard失败: %s")
rc.ResData = total
}
func (r *Redis) Sscan(rc *req.Ctx) {
scan := req.BindJsonAndValid(rc, new(form.ScanForm))
cmd := r.getRedisConn(rc).GetCmdable()
keys, cursor, err := cmd.SScan(context.TODO(), scan.Key, scan.Cursor, scan.Match, scan.Count).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = collx.M{
"keys": keys,
"cursor": cursor,
}
}
func (r *Redis) Sadd(rc *req.Ctx) {
option := req.BindJsonAndValid(rc, new(form.SmemberOption))
cmd := r.getRedisConn(rc).GetCmdable()
res, err := cmd.SAdd(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "sadd失败: %s")
rc.ResData = res
}
func (r *Redis) Srem(rc *req.Ctx) {
option := req.BindJsonAndValid(rc, new(form.SmemberOption))
cmd := r.getRedisConn(rc).GetCmdable()
res, err := cmd.SRem(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "srem失败: %s")
rc.ResData = res
}

View File

@@ -1,29 +0,0 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"time"
)
func (r *Redis) GetStringValue(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
str, err := ri.GetCmdable().Get(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "获取字符串值失败: %s")
rc.ResData = str
}
func (r *Redis) SaveStringValue(rc *req.Ctx) {
keyValue := req.BindJsonAndValid(rc, new(form.StringValue))
ri := r.getRedisConn(rc)
cmd := ri.GetCmdable()
rc.ReqParam = collx.Kvs("redis", ri.Info, "string", keyValue)
str, err := cmd.Set(context.TODO(), keyValue.Key, keyValue.Value, time.Second*time.Duration(keyValue.Timed)).Result()
biz.ErrIsNilAppendErr(err, "保存字符串值失败: %s")
rc.ResData = str
}

View File

@@ -15,6 +15,7 @@ type Redis struct {
Mode *string `json:"mode"`
SshTunnelMachineId int `json:"sshTunnelMachineId"` // ssh隧道机器id
Remark *string `json:"remark"`
FlowProcdefKey string `json:"flowProcdefKey"`
CreateTime *time.Time `json:"createTime"`
Creator *string `json:"creator"`
CreatorId *int64 `json:"creatorId"`

View File

@@ -1,66 +0,0 @@
package api
import (
"context"
"mayfly-go/internal/redis/api/form"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"github.com/redis/go-redis/v9"
)
func (r *Redis) ZCard(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
total, err := ri.GetCmdable().ZCard(context.TODO(), key).Result()
biz.ErrIsNilAppendErr(err, "zcard失败: %s")
rc.ResData = total
}
func (r *Redis) ZScan(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
cursor := uint64(rc.QueryIntDefault("cursor", 0))
match := rc.QueryDefault("match", "*")
count := rc.QueryIntDefault("count", 50)
keys, cursor, err := ri.GetCmdable().ZScan(context.TODO(), key, cursor, match, int64(count)).Result()
biz.ErrIsNilAppendErr(err, "sscan失败: %s")
rc.ResData = collx.M{
"keys": keys,
"cursor": cursor,
}
}
func (r *Redis) ZRevRange(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
start := rc.QueryIntDefault("start", 0)
stop := rc.QueryIntDefault("stop", 50)
res, err := ri.GetCmdable().ZRevRangeWithScores(context.TODO(), key, int64(start), int64(stop)).Result()
biz.ErrIsNilAppendErr(err, "ZRevRange失败: %s")
rc.ResData = res
}
func (r *Redis) ZRem(rc *req.Ctx) {
option := req.BindJsonAndValid(rc, new(form.SmemberOption))
cmd := r.getRedisConn(rc).GetCmdable()
res, err := cmd.ZRem(context.TODO(), option.Key, option.Member).Result()
biz.ErrIsNilAppendErr(err, "zrem失败: %s")
rc.ResData = res
}
func (r *Redis) ZAdd(rc *req.Ctx) {
option := req.BindJsonAndValid(rc, new(form.ZAddOption))
cmd := r.getRedisConn(rc).GetCmdable()
zm := redis.Z{
Score: option.Score,
Member: option.Member,
}
res, err := cmd.ZAdd(context.TODO(), option.Key, zm).Result()
biz.ErrIsNilAppendErr(err, "zadd失败: %s")
rc.ResData = res
}

View File

@@ -1,12 +1,13 @@
package application
import (
"mayfly-go/internal/redis/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(redisAppImpl), ioc.WithComponentName("RedisApp"))
}
func Init() {
InitRedisFlowHandler()
}

View File

@@ -0,0 +1,14 @@
package application
import (
flowapp "mayfly-go/internal/flow/application"
"mayfly-go/pkg/ioc"
)
const (
RedisRunWriteCmdFlowBizType = "redis_run_write_cmd_flow" // db sql exec flow biz type
)
func InitRedisFlowHandler() {
flowapp.RegisterBizHandler(RedisRunWriteCmdFlowBizType, ioc.Get[Redis]("RedisApp"))
}

View File

@@ -3,20 +3,34 @@ package application
import (
"context"
"mayfly-go/internal/common/consts"
flowapp "mayfly-go/internal/flow/application"
flowentity "mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/redis/domain/entity"
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/internal/redis/rdm"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/jsonx"
"mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
"github.com/redis/go-redis/v9"
)
type RunCmdParam struct {
Id uint64 `json:"id"`
Db int `json:"db"`
Cmd []any `json:"cmd"`
Remark string
}
type Redis interface {
base.App[*entity.Redis]
flowapp.FlowBizHandler
// 分页获取机器脚本信息列表
GetPageList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
@@ -33,12 +47,16 @@ type Redis interface {
// id: 数据库实例id
// db: 库号
GetRedisConn(id uint64, db int) (*rdm.RedisConn, error)
// 执行redis命令
RunCmd(ctx context.Context, redisConn *rdm.RedisConn, cmdParam *RunCmdParam) (any, error)
}
type redisAppImpl struct {
base.AppImpl[*entity.Redis, repository.Redis]
tagApp tagapp.TagTree `inject:"TagTreeApp"`
tagApp tagapp.TagTree `inject:"TagTreeApp"`
procinstApp flowapp.Procinst `inject:"ProcinstApp"`
}
// 注入RedisRepo
@@ -96,7 +114,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, re *entity.Redis, tagIds .
return errorx.NewBiz("该实例已存在")
}
// 如果修改了redis实例的库信息则关闭旧库的连接
if oldRedis.Db != re.Db || oldRedis.SshTunnelMachineId != re.SshTunnelMachineId {
if oldRedis.Db != re.Db || oldRedis.SshTunnelMachineId != re.SshTunnelMachineId || oldRedis.FlowProcdefKey != re.FlowProcdefKey {
for _, dbStr := range strings.Split(oldRedis.Db, ",") {
db, _ := strconv.Atoi(dbStr)
rdm.CloseConn(re.Id, db)
@@ -151,3 +169,54 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
return re.ToRedisInfo(db, r.tagApp.ListTagPathByResource(consts.TagResourceTypeRedis, re.Code)...), nil
})
}
func (r *redisAppImpl) RunCmd(ctx context.Context, redisConn *rdm.RedisConn, cmdParam *RunCmdParam) (any, error) {
if redisConn == nil {
return nil, errorx.NewBiz("redis连接不存在")
}
// 开启工单流程,并且为写入命令,则开启对应审批流程
if procdefKey := redisConn.Info.FlowProcdefKey; procdefKey != "" && rdm.IsWriteCmd(cmdParam.Cmd[0]) {
_, err := r.procinstApp.StartProc(ctx, procdefKey, &flowapp.StarProcParam{
BizType: RedisRunWriteCmdFlowBizType,
BizKey: stringx.Rand(24),
BizForm: jsonx.ToStr(cmdParam),
Remark: cmdParam.Remark,
})
if err != nil {
return nil, err
}
return nil, nil
}
res, err := redisConn.RunCmd(ctx, cmdParam.Cmd...)
// 获取的key不存在不报错
if err == redis.Nil {
return nil, nil
}
return res, err
}
func (r *redisAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowapp.BizHandleParam) error {
bizKey := bizHandleParam.BizKey
procinstStatus := bizHandleParam.ProcinstStatus
logx.Debugf("RedisRunWriteCmd FlowBizHandle -> bizKey: %s, procinstStatus: %s", bizKey, flowentity.ProcinstStatusEnum.GetDesc(procinstStatus))
// 流程非完成状态,不处理
if procinstStatus != flowentity.ProcinstStatusCompleted {
return nil
}
runCmdParam, err := jsonx.To(bizHandleParam.BizForm, new(RunCmdParam))
if err != nil {
return errorx.NewBiz("业务表单信息解析失败: %s", err.Error())
}
redisConn, err := r.GetRedisConn(runCmdParam.Id, runCmdParam.Db)
if err != nil {
return err
}
_, err = redisConn.RunCmd(ctx, runCmdParam.Cmd...)
return err
}

View File

@@ -1,17 +1,11 @@
package entity
import "mayfly-go/pkg/model"
type RedisQuery struct {
model.Model
Id uint64 `form:"id"`
Name string `orm:"column(name)" json:"name" form:"name"`
Host string `orm:"column(host)" json:"host" form:"host"`
Name string `orm:"column(name)" json:"name"`
Host string `orm:"column(host)" json:"host"`
Mode string `json:"mode"`
Password string `orm:"column(password)" json:"-"`
Db string `orm:"column(database)" json:"db"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Codes []string
TagPath string `form:"tagPath"`

View File

@@ -20,6 +20,7 @@ type Redis struct {
Db string `orm:"column(database)" json:"db"`
SshTunnelMachineId int `orm:"column(ssh_tunnel_machine_id)" json:"sshTunnelMachineId"` // ssh隧道机器id
Remark string
FlowProcdefKey *string `json:"flowProcdefKey"` // 审批流-流程定义key有值则说明关键操作需要进行审批执行,使用指针为了方便更新空字符串(取消流程审批)
}
func (r *Redis) PwdEncrypt() error {

View File

@@ -4,6 +4,6 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newRedisRepo(), ioc.WithComponentName("RedisRepo"))
}

View File

@@ -19,6 +19,7 @@ func newRedisRepo() repository.Redis {
// 分页获取机器信息列表
func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
qd := gormx.NewQuery(new(entity.Redis)).
Eq("id", condition.Id).
Like("host", condition.Host).
In("code", condition.Codes)
return gormx.PageQuery(qd, pageParam, toEntity)

View File

@@ -3,10 +3,16 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/redis/application"
"mayfly-go/internal/redis/infrastructure/persistence"
"mayfly-go/internal/redis/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
initialize.AddInitFunc(application.Init)
}

View File

@@ -0,0 +1,100 @@
package rdm
import (
"mayfly-go/pkg/utils/anyx"
)
// write cmd
var writeCmd = map[string]string{
"APPEND": "APPEND key value",
"BLMOVE": "BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout",
"BLPOP": "BLPOP key [key ...] timeout",
"BRPOP": "BRPOP key [key ...] timeout",
"BRPOPLPUSH": "BRPOPLPUSH source destination timeout",
"BZPOPMAX": "BZPOPMAX key [key ...] timeout",
"BZPOPMIN": "BZPOPMIN key [key ...] timeout",
"COPY": "COPY source destination [DB destination-db] [REPLACE]",
"DECR": "DECR key",
"DECRBY": "DECRBY key decrement",
"DEL": "DEL key [key ...]",
"EVAL": "EVAL script numkeys key [key ...] arg [arg ...]",
"EVALSHA": "EVALSHA sha1 numkeys key [key ...] arg [arg ...]",
"EXPIRE": "EXPIRE key seconds",
"EXPIREAT": "EXPIREAT key timestamp",
"FLUSHALL": "FLUSHALL",
"FLUSHDB": "FLUSHDB",
"GEOADD": "GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member ...]",
"GETDEL": "GETDEL key",
"GETSET": "GETSET key value",
"HDEL": "HDEL key field [field ...]",
"HINCRBY": "HINCRBY key field increment",
"HINCRBYFLOAT": "HINCRBYFLOAT key field increment",
"HMSET": "HMSET key field value [field value ...]",
"HSET": "HSET key field value",
"HSETNX": "HSETNX key field value",
"INCR": "INCR key",
"INCRBY": "INCRBY key increment",
"INCRBYFLOAT": "INCRBYFLOAT key increment",
"LINSERT": "LINSERT key BEFORE|AFTER pivot value",
"LMOVE": "LMOVE source destination LEFT|RIGHT LEFT|RIGHT",
"LPOP": "LPOP key",
"LPUSH": "LPUSH key value [value ...]",
"LPUSHX": "LPUSHX key value",
"LREM": "LREM key count value",
"LSET": "LSET key index value",
"LTRIM": "LTRIM key start stop",
"MIGRATE": "MIGRATE host port key destination-db timeout",
"MOVE": "MOVE key db",
"MSET": "MSET key value [key value ...]",
"MSETNX": "MSETNX key value [key value ...]",
"PERSIST": "PERSIST key",
"PEXPIRE": "PEXPIRE key milliseconds",
"PEXPIREAT": "PEXPIREAT key milliseconds-timestamp",
"PSETEX": "PSETEX key milliseconds value",
"PUBLISH": "PUBLISH channel message",
"RENAME": "RENAME key newkey",
"RENAMENX": "RENAMENX key newkey",
"RESTORE": "RESTORE key ttl serialized-value",
"RPOP": "RPOP key",
"RPOPLPUSH": "RPOPLPUSH source destination",
"RPUSH": "RPUSH key value [value ...]",
"RPUSHX": "RPUSHX key value",
"SADD": "SADD key member [member ...]",
"SCRIPT": "SCRIPT EXISTS script [script ...], 'SCRIPT FLUSH', 'SCRIPT KILL', SCRIPT LOAD script",
"SDIFFSTORE": "SDIFFSTORE destination key [key ...]",
"SET": "SET key value",
"SETBIT": "SETBIT key offset value",
"SETEX": "SETEX key seconds value",
"SETNX": "SETNX key value",
"SETRANGE": "SETRANGE key offset value",
"SINTERSTORE": "SINTERSTORE destination key [key ...]",
"SMOVE": "SMOVE source destination member",
"SORT": "SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]",
"SPOP": "SPOP key",
"SREM": "SREM key member [member ...]",
"SUNIONSTORE": "SUNIONSTORE destination key [key ...]",
"SWAPDB": "SWAPDB index1 index2",
"UNLINK": "UNLINK key [key ...]",
"XADD": "XADD key ID field string [field string ...]",
"XDEL": "XDEL key ID [ID ...]",
"XGROUP": "XGROUP CREATE key groupname id|$ [MKSTREAM], XGROUP CREATECONSUMER key groupname consumername, XGROUP DELCONSUMER key groupname consumername, XGROUP DESTROY key groupname, XGROUP SETID key groupname id|$",
"XTRIM": "XTRIM key MAXLEN [~] count",
"ZADD": "ZADD key score member [score] [member]",
"ZDIFFSTORE": "ZDIFFSTORE destination numkeys key [key ...]",
"ZINCRBY": "ZINCRBY key increment member",
"ZINTERSTORE": "ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]",
"ZPOPMAX": "ZPOPMAX key [count]",
"ZPOPMIN": "ZPOPMIN key [count]",
"ZRANGESTORE": "ZRANGESTORE dst src min max [BYSCORE|BYLEX] [REV] [LIMIT offset count]",
"ZREM": "ZREM key member [member ...]",
"ZREMRANGEBYLEX": "ZREMRANGEBYLEX key min max",
"ZREMRANGEBYRANK": "ZREMRANGEBYRANK key start stop",
"ZREMRANGEBYSCORE": "ZREMRANGEBYSCORE key min max",
"ZUNIONSTORE": "ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]",
}
// 判断命令是否写命令
func IsWriteCmd(cmd any) bool {
_, ok := writeCmd[anyx.ConvString(cmd)]
return ok
}

View File

@@ -2,6 +2,7 @@ package rdm
import (
"context"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/logx"
"github.com/redis/go-redis/v9"
@@ -32,6 +33,19 @@ func (r *RedisConn) Scan(cursor uint64, match string, count int64) ([]string, ui
return r.GetCmdable().Scan(context.Background(), cursor, match, count).Result()
}
// 执行redis命令
// 如: SET str value命令则args为['SET', 'str', 'val']
func (r *RedisConn) RunCmd(ctx context.Context, args ...any) (any, error) {
redisMode := r.Info.Mode
if redisMode == "" || redisMode == StandaloneMode || r.Info.Mode == SentinelMode {
return r.Cli.Do(ctx, args...).Result()
}
if redisMode == ClusterMode {
return r.ClusterCli.Do(ctx, args...).Result()
}
return nil, errorx.NewBiz("redis mode error")
}
func (r *RedisConn) Close() {
mode := r.Info.Mode
if mode == StandaloneMode || mode == SentinelMode {

View File

@@ -34,6 +34,8 @@ type RedisInfo struct {
Name string `json:"-"`
TagPath []string `json:"tagPath"`
SshTunnelMachineId int `json:"-"`
FlowProcdefKey string // 工单流程定义key
}
func (r *RedisInfo) Conn() (*RedisConn, error) {

View File

@@ -18,11 +18,6 @@ func InitRedisRouter(router *gin.RouterGroup) {
dashbord := new(api.Dashbord)
biz.ErrIsNil(ioc.Inject(dashbord))
// 保存数据权限
saveDataP := req.NewPermission("redis:data:save")
// 删除数据权限
deleteDataP := req.NewPermission("redis:data:del")
reqs := [...]*req.Conf{
req.NewGet("dashbord", dashbord.Dashbord),
@@ -41,6 +36,8 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewGet(":id/cluster-info", rs.ClusterInfo),
req.NewPost(":id/:db/run-cmd", rs.RunCmd).Log(req.NewLogSave("redis-runCmd")),
// 获取指定redis keys
req.NewPost(":id/:db/scan", rs.ScanKeys),
@@ -49,69 +46,7 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewGet(":id/:db/key-ttl", rs.TtlKey),
req.NewGet(":id/:db/key-memuse", rs.MemoryUsage),
req.NewDelete(":id/:db/key", rs.DeleteKey).Log(req.NewLogSave("redis-删除key")).RequiredPermission(deleteDataP),
req.NewPost(":id/:db/rename-key", rs.RenameKey).Log(req.NewLogSave("redis-重命名key")).RequiredPermission(saveDataP),
req.NewPost(":id/:db/expire-key", rs.ExpireKey).Log(req.NewLogSave("redis-设置key过期时间")).RequiredPermission(saveDataP),
req.NewDelete(":id/:db/persist-key", rs.PersistKey).Log(req.NewLogSave("redis-移除key过期时间")).RequiredPermission(saveDataP),
req.NewDelete(":id/:db/flushdb", rs.FlushDb).Log(req.NewLogSave("redis-flushdb")).RequiredPermission(deleteDataP),
// 获取string类型值
req.NewGet(":id/:db/string-value", rs.GetStringValue),
// 设置string类型值
req.NewPost(":id/:db/string-value", rs.SaveStringValue).Log(req.NewLogSave("redis-setString")).RequiredPermission(saveDataP),
// ———————————————— hash操作 ————————————————
req.NewGet(":id/:db/hscan", rs.Hscan),
req.NewGet(":id/:db/hget", rs.Hget),
req.NewPost(":id/:db/hset", rs.Hset).Log(req.NewLogSave("redis-hset")).RequiredPermission(saveDataP),
req.NewDelete(":id/:db/hdel", rs.Hdel).Log(req.NewLogSave("redis-hdel")).RequiredPermission(deleteDataP),
// 设置hash类型值
req.NewPost(":id/:db/hash-value", rs.SaveHashValue).Log(req.NewLogSave("redis-setHashValue")).RequiredPermission(saveDataP),
// --------------- set操作 ----------------
req.NewGet(":id/:db/set-value", rs.GetSetValue),
req.NewPost(":id/:db/set-value", rs.SaveSetValue).RequiredPermission(saveDataP),
req.NewGet(":id/:db/scard", rs.Scard),
req.NewPost(":id/:db/sscan", rs.Sscan),
req.NewPost(":id/:db/sadd", rs.Sadd).RequiredPermission(saveDataP),
req.NewPost(":id/:db/srem", rs.Srem).RequiredPermission(deleteDataP),
// --------------- list操作 ----------------
req.NewGet(":id/:db/list-value", rs.GetListValue),
req.NewPost(":id/:db/list-value", rs.SaveListValue).RequiredPermission(saveDataP),
req.NewPost(":id/:db/list-value/lset", rs.Lset).RequiredPermission(saveDataP),
req.NewPost(":id/:db/lrem", rs.Lrem).RequiredPermission(deleteDataP),
// --------------- zset操作 ----------------
req.NewGet(":id/:db/zcard", rs.ZCard),
req.NewGet(":id/:db/zscan", rs.ZScan),
req.NewGet(":id/:db/zrevrange", rs.ZRevRange),
req.NewPost(":id/:db/zrem", rs.ZRem).Log(req.NewLogSave("redis-zrem")).RequiredPermission(deleteDataP),
req.NewPost(":id/:db/zadd", rs.ZAdd).RequiredPermission(saveDataP),
}
req.BatchSetGroup(redis, reqs[:])
}

View File

@@ -15,6 +15,7 @@ import (
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/conv"
"mayfly-go/pkg/utils/cryptox"
"mayfly-go/pkg/utils/structx"
"strconv"
"strings"
"time"
@@ -143,6 +144,18 @@ func (a *Account) SimpleAccounts(rc *req.Ctx) {
rc.ResData = res
}
// 获取账号详情
func (a *Account) AccountDetail(rc *req.Ctx) {
accountId := uint64(rc.PathParamInt("id"))
account, err := a.AccountApp.GetById(new(entity.Account), accountId)
biz.ErrIsNil(err, "账号不存在")
accountvo := new(vo.SimpleAccountVO)
structx.Copy(accountvo, account)
accountvo.Roles = a.getAccountRoles(accountId)
rc.ResData = accountvo
}
// @router /accounts
func (a *Account) SaveAccount(rc *req.Ctx) {
form := &form.AccountCreateForm{}

View File

@@ -19,6 +19,8 @@ type SimpleAccountVO struct {
Id uint64 `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
Roles []*AccountRoleVO `json:"roles" gorm:"-"`
}
// 账号角色信息

View File

@@ -1,13 +1,10 @@
package application
import (
"mayfly-go/internal/sys/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(accountAppImpl), ioc.WithComponentName("AccountApp"))
ioc.Register(new(roleAppImpl), ioc.WithComponentName("RoleApp"))
ioc.Register(new(configAppImpl), ioc.WithComponentName("ConfigApp"))

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newAccountRepo(), ioc.WithComponentName("AccountRepo"))
ioc.Register(newRoleRepo(), ioc.WithComponentName("RoleRepo"))
ioc.Register(newAccountRoleRepo(), ioc.WithComponentName("AccountRoleRepo"))

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/infrastructure/persistence"
"mayfly-go/internal/sys/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -37,6 +37,9 @@ func InitAccountRouter(router *gin.RouterGroup) {
// 获取用户列表信息(只包含最基础信息)
req.NewGet("/simple", a.SimpleAccounts),
// 根据账号id获取账号基础信息
req.NewGet("/:id", a.AccountDetail),
req.NewPost("", a.SaveAccount).Log(req.NewLogSave("保存账号信息")).RequiredPermission(addAccountPermission),
req.NewPut("change-status/:id/:status", a.ChangeStatus).Log(req.NewLogSave("修改账号状态")).RequiredPermission(addAccountPermission),

View File

@@ -1,13 +1,10 @@
package application
import (
"mayfly-go/internal/tag/infrastructure/persistence"
"mayfly-go/pkg/ioc"
)
func InitIoc() {
persistence.Init()
ioc.Register(new(tagTreeAppImpl), ioc.WithComponentName("TagTreeApp"))
ioc.Register(new(teamAppImpl), ioc.WithComponentName("TeamApp"))
ioc.Register(new(tagResourceAppImpl), ioc.WithComponentName("TagResourceApp"))

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/pkg/ioc"
)
func Init() {
func InitIoc() {
ioc.Register(newTagTreeRepo(), ioc.WithComponentName("TagTreeRepo"))
ioc.Register(newTagTreeTeamRepo(), ioc.WithComponentName("TagTreeTeamRepo"))
ioc.Register(newTagResourceRepo(), ioc.WithComponentName("TagResourceRepo"))

View File

@@ -3,10 +3,14 @@ package init
import (
"mayfly-go/initialize"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/infrastructure/persistence"
"mayfly-go/internal/tag/router"
)
func init() {
initialize.AddInitIocFunc(application.InitIoc)
initialize.AddInitIocFunc(func() {
persistence.InitIoc()
application.InitIoc()
})
initialize.AddInitRouterFunc(router.Init)
}

View File

@@ -617,6 +617,7 @@ CREATE TABLE IF NOT EXISTS "t_redis" (
"mode" text(32),
"ssh_tunnel_machine_id" integer(20),
"remark" text(125),
"flow_procdef_key" text(64),
"creator" text(32),
"creator_id" integer(32),
"create_time" datetime,
@@ -1024,6 +1025,7 @@ CREATE TABLE "t_flow_procinst" (
"status" tinyint DEFAULT NULL ,
"biz_type" text(64) NOT NULL ,
"biz_key" text(64) NOT NULL ,
"biz_form" text(5000) DEFAULT NULL ,
"biz_status" tinyint DEFAULT NULL ,
"biz_handle_res" text(100) DEFAULT NULL ,
"remark" text(191) DEFAULT NULL,

View File

@@ -531,6 +531,7 @@ CREATE TABLE `t_redis` (
`mode` varchar(32) DEFAULT NULL,
`ssh_tunnel_machine_id` bigint(20) DEFAULT NULL COMMENT 'ssh隧道的机器id',
`remark` varchar(125) DEFAULT NULL,
`flow_procdef_key` varchar(100) DEFAULT NULL COMMENT '工单流程定义key',
`creator` varchar(32) DEFAULT NULL,
`creator_id` bigint(32) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
@@ -1045,8 +1046,9 @@ CREATE TABLE `t_flow_procinst` (
`status` tinyint DEFAULT NULL COMMENT '状态',
`biz_type` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务类型',
`biz_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务key',
`biz_form` text DEFAULT NULL COMMENT '业务form',
`biz_status` tinyint DEFAULT NULL COMMENT '业务状态',
`biz_handle_res` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`biz_handle_res` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '流程持续时间(开始到结束)',

View File

@@ -35,8 +35,9 @@ CREATE TABLE `t_flow_procinst` (
`status` tinyint DEFAULT NULL COMMENT '状态',
`biz_type` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT '关联业务类型',
`biz_key` varchar(64) NOT NULL COMMENT '关联业务key',
`biz_form` text DEFAULT NULL COMMENT '业务form',
`biz_status` tinyint DEFAULT NULL COMMENT '业务状态',
`biz_handle_res` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`biz_handle_res` varchar(1000) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '关联的业务处理结果',
`remark` varchar(191) DEFAULT NULL,
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`duration` bigint DEFAULT NULL COMMENT '流程持续时间(开始到结束)',
@@ -80,6 +81,7 @@ ALTER TABLE t_db_sql_exec ADD flow_biz_key varchar(64) NULL COMMENT '工单流
ALTER TABLE t_db_sql_exec ADD res varchar(1000) NULL COMMENT '执行结果';
ALTER TABLE t_db ADD flow_procdef_key varchar(64) NULL COMMENT '审批流-流程定义key有值则说明关键操作需要进行审批执行';
ALTER TABLE t_redis ADD flow_procdef_key varchar(64) NULL COMMENT '审批流-流程定义key有值则说明关键操作需要进行审批执行';
-- 历史执行记录调整为成功状态
UPDATE t_db_sql_exec SET status = 2;