feat: 回滚字典值.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package com.orion.ops.framework.common.validator.group;
|
||||
|
||||
import javax.validation.groups.Default;
|
||||
|
||||
/**
|
||||
* 分页验证分组
|
||||
*
|
||||
@@ -7,5 +9,5 @@ package com.orion.ops.framework.common.validator.group;
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/1 19:13
|
||||
*/
|
||||
public interface Id {
|
||||
public interface Id extends Default {
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.orion.ops.framework.common.validator.group;
|
||||
|
||||
import javax.validation.groups.Default;
|
||||
|
||||
/**
|
||||
* 分页验证分组
|
||||
*
|
||||
@@ -7,5 +9,5 @@ package com.orion.ops.framework.common.validator.group;
|
||||
* @version 1.0.0
|
||||
* @since 2023/9/1 19:13
|
||||
*/
|
||||
public interface Page {
|
||||
public interface Page extends Default {
|
||||
}
|
||||
|
||||
@@ -120,8 +120,8 @@ logging:
|
||||
max-file-size: 16MB
|
||||
total-size-cap: 0B
|
||||
pattern:
|
||||
console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %boldBlue([%X{tid}]) %clr([%22.22t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'
|
||||
file: "%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} [%X{tid}] [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
|
||||
console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%6p}) %boldBlue([%X{tid}]) %clr([%22.22t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'
|
||||
file: "%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%6p} [%X{tid}] [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
|
||||
level:
|
||||
com.orion.ops.launch.controller.BootstrapController: INFO
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ public class DictValueOperatorType extends InitializingOperatorTypes {
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(L, CREATE, "创建字典配置值 <sb>${keyName}</sb> <sb>${label}=${value}</sb>"),
|
||||
new OperatorType(M, UPDATE, "更新字典配置值 <sb>${keyName}</sb> <sb>${label}=${value}</sb>"),
|
||||
new OperatorType(L, CREATE, "创建字典配置值 <sb>${keyName}</sb>: <sb>${label}</sb> | <sb>${value}</sb>"),
|
||||
new OperatorType(M, UPDATE, "更新字典配置值 <sb>${keyName}</sb>: <sb>${label}</sb> | <sb>${value}</sb>"),
|
||||
new OperatorType(H, DELETE, "删除字典配置值 <sb>${value}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.orion.ops.framework.common.entity.PageRequest;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@@ -27,7 +28,7 @@ public class HistoryValueQueryRequest extends PageRequest {
|
||||
private Long relId;
|
||||
|
||||
@Size(max = 16)
|
||||
@NotNull
|
||||
@NotBlank
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.orion.lang.utils.collect.Lists;
|
||||
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs;
|
||||
import com.orion.ops.framework.common.constant.Const;
|
||||
import com.orion.ops.framework.common.constant.ErrorMessage;
|
||||
import com.orion.ops.framework.common.constant.FieldConst;
|
||||
import com.orion.ops.framework.common.utils.Valid;
|
||||
import com.orion.ops.framework.mybatis.core.query.Conditions;
|
||||
import com.orion.ops.framework.redis.core.utils.RedisStrings;
|
||||
@@ -98,6 +99,7 @@ public class DictValueServiceImpl implements DictValueService {
|
||||
this.checkDictValuePresent(updateRecord);
|
||||
// 更新
|
||||
OperatorLogs.add(OperatorLogs.KEY_NAME, dictKey);
|
||||
OperatorLogs.add(OperatorLogs.VALUE, this.getDictValueJson(updateRecord));
|
||||
updateRecord.setKeyName(key);
|
||||
int effect = dictValueDAO.updateById(updateRecord);
|
||||
log.info("DictValueService-updateDictValueById effect: {}", effect);
|
||||
@@ -118,6 +120,9 @@ public class DictValueServiceImpl implements DictValueService {
|
||||
// 查询历史值
|
||||
HistoryValueDO history = historyValueService.getHistoryByRelId(request.getValueId(), id, HistoryValueTypeEnum.DICT.name());
|
||||
Valid.notNull(history, ErrorMessage.HISTORY_ABSENT);
|
||||
JSONObject historyValue = JSON.parseObject(history.getBeforeValue());
|
||||
String label = (String) historyValue.remove(OperatorLogs.LABEL);
|
||||
String value = (String) historyValue.remove(OperatorLogs.VALUE);
|
||||
// 记录日志参数
|
||||
OperatorLogs.add(OperatorLogs.KEY_NAME, record.getKeyName());
|
||||
OperatorLogs.add(OperatorLogs.LABEL, record.getLabel());
|
||||
@@ -125,7 +130,9 @@ public class DictValueServiceImpl implements DictValueService {
|
||||
// 更新
|
||||
DictValueDO updateRecord = new DictValueDO();
|
||||
updateRecord.setId(id);
|
||||
updateRecord.setValue(history.getBeforeValue());
|
||||
updateRecord.setLabel(label);
|
||||
updateRecord.setValue(value);
|
||||
updateRecord.setExtra(historyValue.toString());
|
||||
int effect = dictValueDAO.updateById(updateRecord);
|
||||
log.info("DictValueService-rollbackDictValueById effect: {}", effect);
|
||||
// 删除缓存
|
||||
@@ -306,9 +313,11 @@ public class DictValueServiceImpl implements DictValueService {
|
||||
* @param record 修改前
|
||||
*/
|
||||
private void checkRecordHistory(DictValueDO updateRecord, DictValueDO record) {
|
||||
// 原始值
|
||||
String beforeValue = this.getDictValueJson(record);
|
||||
// 新值
|
||||
String afterValue = this.getDictValueJson(updateRecord);
|
||||
// 检查值是否发生改变
|
||||
String beforeValue = record.getValue();
|
||||
String afterValue = updateRecord.getValue();
|
||||
if (!beforeValue.equals(afterValue)) {
|
||||
// 记录历史值
|
||||
HistoryValueCreateRequest historyRequest = new HistoryValueCreateRequest();
|
||||
@@ -320,6 +329,21 @@ public class DictValueServiceImpl implements DictValueService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典值 json
|
||||
*
|
||||
* @param record record
|
||||
* @return value
|
||||
*/
|
||||
private String getDictValueJson(DictValueDO record) {
|
||||
JSONObject beforeValue = Optional.ofNullable(record.getExtra())
|
||||
.map(JSON::parseObject)
|
||||
.orElseGet(JSONObject::new);
|
||||
beforeValue.put(FieldConst.VALUE, record.getValue());
|
||||
beforeValue.put(FieldConst.LABEL, record.getLabel());
|
||||
return beforeValue.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否存在
|
||||
*
|
||||
|
||||
@@ -97,8 +97,8 @@ public class HistoryValueServiceImpl implements HistoryValueService {
|
||||
.eq(HistoryValueDO::getRelId, request.getRelId())
|
||||
.eq(HistoryValueDO::getType, request.getType())
|
||||
.and(Strings.isNotEmpty(searchValue), c -> c
|
||||
.eq(HistoryValueDO::getBeforeValue, searchValue).or()
|
||||
.eq(HistoryValueDO::getAfterValue, searchValue)
|
||||
.like(HistoryValueDO::getBeforeValue, searchValue).or()
|
||||
.like(HistoryValueDO::getAfterValue, searchValue)
|
||||
)
|
||||
.orderByDesc(HistoryValueDO::getId);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface DictValueUpdateRequest extends DictValueCreateRequest {
|
||||
*/
|
||||
export interface DictValueRollbackRequest {
|
||||
id?: number;
|
||||
relId?: number;
|
||||
valueId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
217
orion-ops-ui/src/components/meta/history/history-value-modal.vue
Normal file
217
orion-ops-ui/src/components/meta/history/history-value-modal.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
body-class="modal-form"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:width="960"
|
||||
:top="80"
|
||||
:body-style="{padding: '0 8px'}"
|
||||
:align-center="false"
|
||||
:draggable="true"
|
||||
:mask-closable="false"
|
||||
:unmount-on-close="true"
|
||||
:footer="false"
|
||||
@close="handleClose">
|
||||
<a-card class="general-card table-card">
|
||||
<template #title>
|
||||
<!-- 左侧操作 -->
|
||||
<div class="table-left-bar-handle">
|
||||
</div>
|
||||
<!-- 右侧操作 -->
|
||||
<div class="table-right-bar-handle">
|
||||
<a-space>
|
||||
<a-input-search v-model="form.searchValue"
|
||||
placeholder="搜索值"
|
||||
@search="() => fetchTableData()"
|
||||
@keyup.enter="() => fetchTableData()" />
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
<!-- table -->
|
||||
<a-table row-key="id"
|
||||
class="table-wrapper-8"
|
||||
ref="tableRef"
|
||||
label-align="left"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data="tableRenderData"
|
||||
:pagination="pagination"
|
||||
@page-change="(page) => fetchTableData(page, pagination.pageSize)"
|
||||
@page-size-change="(size) => fetchTableData(1, size)"
|
||||
:bordered="false">
|
||||
<!-- 修改前 -->
|
||||
<template #beforeValue="{ record }">
|
||||
<span class="copy-left" title="复制" @click="copy(record.beforeValue)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span>{{ record.beforeValue }}</span>
|
||||
</template>
|
||||
<!-- 修改后 -->
|
||||
<template #afterValue="{ record }">
|
||||
<span class="copy-left" title="复制" @click="copy(record.afterValue)">
|
||||
<icon-copy />
|
||||
</span>
|
||||
<span>{{ record.afterValue }}</span>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #handle="{ record }">
|
||||
<div class="table-handle-wrapper">
|
||||
<!-- 回滚 -->
|
||||
<a-popconfirm content="确认要回滚到当前记录?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="rollbackValue(record)">
|
||||
<a-button type="text" size="mini">
|
||||
回滚
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'history-value-modal'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
import { reactive, ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import { getHistoryValuePage, HistoryValueQueryRequest, HistoryValueQueryResponse } from '@/api/meta/history-value';
|
||||
import { usePagination } from '@/types/table';
|
||||
import useCopy from '@/hooks/copy';
|
||||
import { dateFormat } from '@/utils';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
dataIndex: 'id',
|
||||
slotName: 'id',
|
||||
width: 70,
|
||||
align: 'left',
|
||||
fixed: 'left',
|
||||
}, {
|
||||
title: '修改前',
|
||||
dataIndex: 'beforeValue',
|
||||
slotName: 'beforeValue',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '修改后',
|
||||
dataIndex: 'afterValue',
|
||||
slotName: 'afterValue',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'createTime',
|
||||
slotName: 'createTime',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
render: ({ record }) => {
|
||||
return dateFormat(new Date(record.createTime));
|
||||
},
|
||||
}, {
|
||||
title: '修改人',
|
||||
dataIndex: 'creator',
|
||||
slotName: 'creator',
|
||||
width: 80,
|
||||
}, {
|
||||
title: '操作',
|
||||
slotName: 'handle',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
},
|
||||
] as TableColumnData[];
|
||||
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
rollback: Function
|
||||
});
|
||||
const emits = defineEmits(['updated']);
|
||||
|
||||
const { copy } = useCopy();
|
||||
const pagination = usePagination();
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const title = ref<string>();
|
||||
const tableRenderData = ref<HistoryValueQueryResponse[]>([]);
|
||||
const form = reactive<HistoryValueQueryRequest>({
|
||||
searchValue: undefined,
|
||||
relId: undefined,
|
||||
});
|
||||
|
||||
// 打开
|
||||
const open = (id: number, value: string) => {
|
||||
form.relId = id;
|
||||
title.value = `历史值 - ${value}`;
|
||||
setVisible(true);
|
||||
fetchTableData();
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
|
||||
// 回滚
|
||||
const rollbackValue = async (record: HistoryValueQueryResponse) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await (props.rollback as Function)(form.relId, record.id);
|
||||
Message.success('回滚成功');
|
||||
emits('updated');
|
||||
handleClear();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载数据
|
||||
const doFetchTableData = async (request: HistoryValueQueryRequest) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await getHistoryValuePage({
|
||||
...request,
|
||||
type: props.type,
|
||||
...form
|
||||
});
|
||||
tableRenderData.value = data.rows;
|
||||
pagination.total = data.total;
|
||||
pagination.current = request.page;
|
||||
pagination.pageSize = request.limit;
|
||||
} catch (e) {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 切换页码
|
||||
const fetchTableData = (page = 1, limit = pagination.pageSize) => {
|
||||
doFetchTableData({ page, limit });
|
||||
};
|
||||
|
||||
// 关闭回调
|
||||
const handleClose = () => {
|
||||
handleClear();
|
||||
};
|
||||
|
||||
// 清空
|
||||
const handleClear = () => {
|
||||
setLoading(false);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
</style>
|
||||
@@ -3,11 +3,17 @@
|
||||
<!-- 列表-表格 -->
|
||||
<dict-value-table ref="table"
|
||||
@openAdd="() => modal.openAdd()"
|
||||
@openUpdate="(e) => modal.openUpdate(e)" />
|
||||
@openUpdate="(e) => modal.openUpdate(e)"
|
||||
@openHistory="(e) => history.open(e.id, e.label)" />
|
||||
<!-- 添加修改模态框 -->
|
||||
<dict-value-form-modal ref="modal"
|
||||
@added="modalAddCallback"
|
||||
@updated="modalUpdateCallback" />
|
||||
<!-- 历史值模态框 -->
|
||||
<history-value-modal ref="history"
|
||||
:type="historyType"
|
||||
:rollback="rollback"
|
||||
@updated="modalUpdateCallback" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,15 +26,18 @@
|
||||
<script lang="ts" setup>
|
||||
import DictValueTable from './components/dict-value-table.vue';
|
||||
import DictValueFormModal from './components/dict-value-form-modal.vue';
|
||||
|
||||
import HistoryValueModal from '@/components/meta/history/history-value-modal.vue';
|
||||
import { ref, onBeforeMount, onUnmounted } from 'vue';
|
||||
import { historyType } from './types/const';
|
||||
import { useCacheStore } from '@/store';
|
||||
import { getDictKeyList } from '@/api/system/dict-key';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { rollbackDictValue } from '@/api/system/dict-value';
|
||||
|
||||
const render = ref(false);
|
||||
const table = ref();
|
||||
const modal = ref();
|
||||
const history = ref();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
// 添加回调
|
||||
@@ -41,6 +50,11 @@
|
||||
table.value.updatedCallback();
|
||||
};
|
||||
|
||||
// 回滚
|
||||
const rollback = async (id: number, valueId: number) => {
|
||||
await rollbackDictValue({ id, valueId });
|
||||
};
|
||||
|
||||
// 加载字典配置项
|
||||
const loadDictKeys = async () => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// 历史值类型
|
||||
export const historyType = 'DICT';
|
||||
|
||||
Reference in New Issue
Block a user