feat(constool): 添加数据对比同步工具并重构动态表格构建器
- 新增 KitDiffSyncConstTool 工具类,支持集合数据的对比同步功能 - 添加数据同步回调接口和完整的增删改查批量操作支持 - 重命名 KitDynamicTableUtil 为 KitDynamicTableBuilder 并调整方法名称 - 修改 renderOrder 方法为 ofOrder,renderAll 方法为 ofAll - 更新前端 FormatDuration 函数,支持年、天、时、分、秒格式化显示 - 添加字节格式化方法 renderBytes 到 KitStringUtil 工具类 - 在首页视图中集成 Pipeline 组件用于展示任务流程 - 添加窗口打开工具函数 OpenWindowInCurrent 和 OpenWindowInNew - 创建 MybatisMapperBatchOpRender 批量操作生成器
This commit is contained in:
@@ -273,3 +273,11 @@ export function IsNotBlank(str) {
|
||||
export function IsBlank(str) {
|
||||
return str == null || String(str).trim().length === 0
|
||||
}
|
||||
|
||||
export function OpenWindowInCurrent(url) {
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
export function OpenWindowInNew(url) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
@@ -390,22 +390,31 @@ export function FormatEnabled(value) {
|
||||
*/
|
||||
export function FormatDuration(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
return ''
|
||||
}
|
||||
const seconds = Number(value)
|
||||
const seconds = Number(value / 1000)
|
||||
if (isNaN(seconds)) {
|
||||
return '-'
|
||||
return ''
|
||||
}
|
||||
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const years = Math.floor(seconds / (365 * 24 * 3600))
|
||||
const days = Math.floor((seconds % (365 * 24 * 3600)) / (24 * 3600))
|
||||
const hours = Math.floor((seconds % (24 * 3600)) / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = seconds % 60
|
||||
const secs = (seconds % 60).toFixed(0)
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}小时${minutes}分${secs}秒`
|
||||
if (years > 0) {
|
||||
return `${years}年${days}天${hours}时${minutes}分${secs}秒`
|
||||
} else if (days > 0) {
|
||||
return `${days}天${hours}时${minutes}分${secs}秒`
|
||||
} else if (hours > 0) {
|
||||
return `${hours}时${minutes}分${secs}秒`
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分${secs}秒`
|
||||
} else {
|
||||
if (secs === '0') {
|
||||
return ``
|
||||
}
|
||||
return `${secs}秒`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div :style="taskWindowStyle">
|
||||
<div :style="taskHeaderStyle" />
|
||||
<div :style="taskBodyStyle">
|
||||
<!-- 标题和图标 -->
|
||||
<el-row :gutter="2">
|
||||
<el-col :span="16">{{ name }}</el-col>
|
||||
<el-col :span="8" style="text-align: right">
|
||||
<i :class="getStatusIconClassByStatus(status)" :style="getStatusIconColorByStatus(status)" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 分割线 -->
|
||||
<div style="padding-left: 10px; padding-right: 10px; padding-top: 2px; border-bottom: 1px solid #EBEEF5" />
|
||||
<!-- 任务执行信息 -->
|
||||
<div
|
||||
v-if="click === false"
|
||||
style="width: 100%; height: 40px; line-height: 30px; font-size: 12px; color: #606266;
|
||||
text-align: center;padding: 9px 0;font-family: Arial, sans-serif"
|
||||
>
|
||||
{{ desc }}
|
||||
</div>
|
||||
<el-button
|
||||
v-else
|
||||
v-prevent-re-click
|
||||
type="text"
|
||||
style="width: 100%; height: 40px; line-height: 30px; font-size: 12px; color: #606266"
|
||||
@click="onTaskDetailClick"
|
||||
>
|
||||
{{ desc }}
|
||||
</el-button>
|
||||
<el-row :gutter="2">
|
||||
<!-- 持续时间 -->
|
||||
<el-col :span="15" style="font-size: 12px;color: #606266;height: 32px;max-height: 32px;line-height: 32px">
|
||||
{{ FormatDuration(status !== statusMap.running ? durationMillis : new Date().getTime() - startTimeMillis) }}
|
||||
</el-col>
|
||||
<!-- 任务失败,且支持重试 -->
|
||||
<el-col v-if="status === statusMap.failed && retry === true" :span="9" style="font-size: 12px;color: #606266;height: 32px;max-height: 32px">
|
||||
<el-button v-prevent-re-click type="text" @click="onRetryClick">重试</el-button>
|
||||
</el-col>
|
||||
<!-- 任务运行中,且包含按钮组 -->
|
||||
<el-col v-if="buttonList && buttonList.length > 0" :span="9" style="font-size: 12px;color: #606266;height: 32px;max-height: 32px;padding-right: 5px">
|
||||
<el-button
|
||||
v-for="(item, index) in buttonList"
|
||||
:key="index"
|
||||
v-prevent-re-click
|
||||
type="text"
|
||||
:style="`width: ${item.text && item.text.length >= 4 ? '74px': '26px'
|
||||
};color: ${item.color ? item.color : statusColorMap.running};display: ${item.condition ===
|
||||
'all' ?
|
||||
'inline-block':
|
||||
(item.condition === status ? 'inline-block': 'none')};overflow: hidden;text-overflow: ellipsis;`"
|
||||
@click="onButtonNodeClick(item)"
|
||||
>{{ item.text }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<!-- 任务节点明细 -->
|
||||
<cute-preview-drawer ref="taskDetailDrawer" :title="name">
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(taskDetail, index) in taskDetails"
|
||||
:key="index"
|
||||
:color="getColorHexByStatus(taskDetail.status)"
|
||||
:timestamp="taskDetail.timestamp"
|
||||
>
|
||||
{{ taskDetail.content }}
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</cute-preview-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import KitMessage from '@/utils/elementui/KitMessage'
|
||||
import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer.vue'
|
||||
import { OpenWindowInCurrent, OpenWindowInNew } from '@/utils/KitUtil'
|
||||
import request from '@/utils/request'
|
||||
import { FormatDuration } from '@/utils/filters'
|
||||
|
||||
export default {
|
||||
name: 'PipeTask',
|
||||
components: { CutePreviewDrawer },
|
||||
props: {
|
||||
/**
|
||||
* 任务名称
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 任务编码
|
||||
*/
|
||||
code: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 开始时间戳。单位:毫秒
|
||||
*/
|
||||
startTimeMillis: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 持续时间。单位:秒
|
||||
*/
|
||||
durationMillis: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 状态(翻译)
|
||||
*/
|
||||
statusDesc: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 任务执行信息
|
||||
*/
|
||||
desc: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 是否可以点击
|
||||
*/
|
||||
click: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 是否可以重试
|
||||
*/
|
||||
retry: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 任务明细类型(gitlab、jenkins等)
|
||||
*/
|
||||
detailType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
/**
|
||||
* 按钮组
|
||||
* @property type: execute(请求系统api)、link(调用三方链接跳转)
|
||||
* @property text: 按钮标题
|
||||
* @property color: 按钮颜色
|
||||
* @property condition: 符合条件才展示,当值为`all`时,非pending状态都展示
|
||||
* @property method: 当type=execute时,配置请求方式:GET/POST
|
||||
* @property requestUrl: 当type=execute时,配置请求路径和参数
|
||||
* @property linkUrl: 当type=link时,配置跳转路径和参数
|
||||
* @property isBlank: 当type=link时,判断是否在新页面中打开
|
||||
*/
|
||||
buttonList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusMap: {
|
||||
pending: 'pending',
|
||||
running: 'running',
|
||||
success: 'success',
|
||||
failed: 'failed'
|
||||
},
|
||||
statusColorMap: {
|
||||
pending: '#EBEEF5',
|
||||
success: '#67C23A',
|
||||
failed: '#F56C6C',
|
||||
running: '#409EFF'
|
||||
},
|
||||
// 任务明细
|
||||
taskDetails: [
|
||||
// { timestamp: '2018-04-03 20:46', content: '开始初始化', status: 'success' },
|
||||
// { timestamp: '2018-04-03 20:46', content: '开始初始化', status: 'success' },
|
||||
// { timestamp: '2018-04-03 20:46', content: '开始初始化', status: 'failed' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
taskWindowStyle() {
|
||||
const colorHex = this.getColorHexByStatus(this.status)
|
||||
return {
|
||||
width: '240px',
|
||||
maxWidth: '240px',
|
||||
height: '100px',
|
||||
maxHeight: '100px',
|
||||
border: `1px solid ${colorHex}`,
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
display: 'inline-block'
|
||||
}
|
||||
},
|
||||
taskHeaderStyle() {
|
||||
const colorHex = this.getColorHexByStatus(this.status)
|
||||
return {
|
||||
width: '100%',
|
||||
height: '5px',
|
||||
backgroundColor: colorHex
|
||||
}
|
||||
},
|
||||
taskBodyStyle() {
|
||||
return {
|
||||
padding: '5px',
|
||||
fontSize: '14px',
|
||||
backgroundColor: '#FFFFFF',
|
||||
color: '#606266',
|
||||
height: '95px'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
FormatDuration,
|
||||
/**
|
||||
* 根据任务状态获取颜色Hex值
|
||||
*/
|
||||
getColorHexByStatus(status) {
|
||||
const colorHex = this.statusColorMap[status]
|
||||
if (colorHex) {
|
||||
return colorHex
|
||||
}
|
||||
return this.statusColorMap.pending
|
||||
},
|
||||
/**
|
||||
* 根据任务状态渲染状态图标样式
|
||||
* @param status
|
||||
*/
|
||||
getStatusIconClassByStatus(status) {
|
||||
if (this.statusMap.success === status) {
|
||||
return 'el-icon-circle-check'
|
||||
}
|
||||
if (this.statusMap.failed === status) {
|
||||
return 'el-icon-circle-close'
|
||||
}
|
||||
if (this.statusMap.running === status) {
|
||||
return 'el-icon-loading'
|
||||
}
|
||||
return 'el-icon-time'
|
||||
},
|
||||
/**
|
||||
* 根据任务状态渲染状态图标颜色
|
||||
* @param status
|
||||
*/
|
||||
getStatusIconColorByStatus(status) {
|
||||
const styles = {
|
||||
width: '14px',
|
||||
height: '14px'
|
||||
}
|
||||
styles.color = this.getColorHexByStatus(status)
|
||||
return styles
|
||||
},
|
||||
onTaskDetailClick() {
|
||||
console.error('当任务卡片可点击时,查看明细记录')
|
||||
if (this.statusMap.pending === this.status) {
|
||||
KitMessage.Error('节点任务未开始')
|
||||
return
|
||||
}
|
||||
if (this.detailType === 'gitlab') {
|
||||
// 获取gitlab任务明细
|
||||
} else if (this.detailType === 'jenkins') {
|
||||
// 获取jenkins任务明细
|
||||
} else {
|
||||
// 获取业务服务明细
|
||||
}
|
||||
this.$refs.taskDetailDrawer.show()
|
||||
},
|
||||
onRetryClick() {
|
||||
console.error('当任务卡片可重试时,任务重试', this.code)
|
||||
this.$emit('retry', this.code)
|
||||
},
|
||||
async onButtonNodeClick(buttonProps) {
|
||||
console.error('当任务卡片包含按钮组时,某个按钮被单击', buttonProps)
|
||||
if (buttonProps.type === 'execute') {
|
||||
const { method, requestUrl } = buttonProps
|
||||
if (method === 'get' || method === 'post') {
|
||||
const response = await request({ url: requestUrl, method: method })
|
||||
console.error('请求系统内置接口结果:', response)
|
||||
} else {
|
||||
KitMessage.Error(`'${buttonProps.text}' 按钮异常,不支持的请求方式`)
|
||||
}
|
||||
} else if (buttonProps.type === 'link') {
|
||||
const { blank, linkUrl } = buttonProps
|
||||
if (blank) {
|
||||
OpenWindowInNew(linkUrl)
|
||||
} else {
|
||||
OpenWindowInCurrent(linkUrl)
|
||||
}
|
||||
} else {
|
||||
KitMessage.Error(`'${buttonProps.text}' 按钮异常,不支持的类型`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<cute-horizontal-scroll-bar :width="maxWidth">
|
||||
<i v-for="(item, index) in taskNodes" :key="index">
|
||||
<!-- 最后一个元素如何渲染 -->
|
||||
<i v-if="index === taskNodes.length - 1">
|
||||
<pipe-task v-bind="item" />
|
||||
</i>
|
||||
<!-- 其他元素如何渲染 -->
|
||||
<i v-else class="pipeline-card">
|
||||
<pipe-task v-bind="item" />
|
||||
<div class="pipeline-link" />
|
||||
</i>
|
||||
</i>
|
||||
</cute-horizontal-scroll-bar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import PipeTask from '@/views/components/devops/pipeline/PipeTask.vue'
|
||||
import CuteHorizontalScrollBar from '@/views/components/dev/CuteHorizontalScrollBar.vue'
|
||||
|
||||
export default {
|
||||
name: 'Pipeline',
|
||||
components: { CuteHorizontalScrollBar, PipeTask },
|
||||
props: {
|
||||
/**
|
||||
* 流水线全局唯一实例ID
|
||||
*/
|
||||
instanceId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 流水线任务节点明细
|
||||
*/
|
||||
taskNodes: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
maxWidth() {
|
||||
return `${document.body.clientWidth - 310}px`
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
mounted() {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pipeline-card {
|
||||
width: 240px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
||||
.pipeline-link {
|
||||
height: 3px;
|
||||
width: 30px;
|
||||
display: inline-block;
|
||||
background-color: #E4E7ED;
|
||||
margin-bottom: 3em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,11 +3,20 @@
|
||||
<github-corner class="github-corner" />
|
||||
|
||||
<el-row :gutter="40" style="background-color: white;margin-left: 2px;margin-right: 2px;padding: 6px">
|
||||
<el-col>系统API文档:<el-link type="primary" :href="swaggerApi" target="_blank">{{ swaggerApi }}</el-link></el-col>
|
||||
<el-col>系统API文档:
|
||||
<el-link type="primary" :href="swaggerApi" target="_blank">{{ swaggerApi }}</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<panel-group @handleSetLineChartData="handleSetLineChartData" />
|
||||
|
||||
<div style="border: 1px solid #E4E7ED;margin-bottom: 10px">
|
||||
<Pipeline instance-id="instanceId1" :task-nodes="task1Nodes" />
|
||||
<Pipeline instance-id="instanceId2" :task-nodes="task2Nodes" />
|
||||
<Pipeline instance-id="instanceId3" :task-nodes="task3Nodes" />
|
||||
<Pipeline instance-id="instanceId4" :task-nodes="task4Nodes" />
|
||||
</div>
|
||||
|
||||
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
|
||||
<line-chart :chart-data="lineChartData" />
|
||||
</el-row>
|
||||
@@ -39,6 +48,7 @@ import RadarChart from '@/components/Echarts/RadarChart'
|
||||
import PieChart from '@/components/Echarts/PieChart'
|
||||
import BarChart from '@/components/Echarts/BarChart'
|
||||
import store from '@/store'
|
||||
import Pipeline from '@/views/components/devops/pipeline/Pipeline.vue'
|
||||
|
||||
const lineChartData = {
|
||||
newVisitis: {
|
||||
@@ -62,6 +72,7 @@ const lineChartData = {
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
Pipeline,
|
||||
GithubCorner,
|
||||
PanelGroup,
|
||||
LineChart,
|
||||
@@ -72,7 +83,391 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
swaggerApi: store.getters.swaggerApi,
|
||||
lineChartData: lineChartData.newVisitis
|
||||
lineChartData: lineChartData.newVisitis,
|
||||
task1Nodes: [
|
||||
{
|
||||
click: false,
|
||||
code: 'release',
|
||||
desc: '执行成功',
|
||||
durationMillis: 7446,
|
||||
name: '环境准备',
|
||||
startTimeMillis: 1652343103245,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'merge',
|
||||
desc: '执行成功',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 50331,
|
||||
name: '合并代码',
|
||||
startTimeMillis: 1652343110691,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: false,
|
||||
code: 'stc_upload',
|
||||
desc: '代码上传成功',
|
||||
durationMillis: 16123,
|
||||
name: 'STC扫描(代码上传)',
|
||||
startTimeMillis: 1652343161022,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'build',
|
||||
desc: '执行成功',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 104692,
|
||||
name: '构建',
|
||||
startTimeMillis: 1652343177145,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'all',
|
||||
blank: true,
|
||||
linkUrl: 'https://devops.odboy.cn/depolyHistory/release?appName=kenaito-pilot&id=1',
|
||||
text: '查看扫描结果',
|
||||
type: 'link'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'stc_result',
|
||||
desc: 'STC扫描通过',
|
||||
durationMillis: 18971,
|
||||
name: 'STC扫描(扫描结果)',
|
||||
startTimeMillis: 1652343281837,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: false,
|
||||
code: 'doPreCheck',
|
||||
desc: '【田俊】确认通过',
|
||||
durationMillis: 69937,
|
||||
name: '发布前确认',
|
||||
startTimeMillis: 1652343300808,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'success',
|
||||
blank: true,
|
||||
linkUrl: 'https://devops.odboy.cn/depolyHistory/release?appName=kenaito-pilot&id=1',
|
||||
text: '查看部署详情',
|
||||
type: 'link'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'deploy',
|
||||
desc: '成功',
|
||||
durationMillis: 161975,
|
||||
name: '部署',
|
||||
startTimeMillis: 1652343370745,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/doCheck?id=60012',
|
||||
text: '确认合并',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'mergeMaster',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 0,
|
||||
name: '合并主干',
|
||||
desc: '合并master确认',
|
||||
startTimeMillis: 0,
|
||||
status: 'running',
|
||||
statusDesc: '执行中'
|
||||
}
|
||||
],
|
||||
task2Nodes: [
|
||||
{
|
||||
click: false,
|
||||
code: 'release',
|
||||
desc: '执行成功',
|
||||
durationMillis: 7446,
|
||||
name: '环境准备',
|
||||
startTimeMillis: 1652343103245,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'merge',
|
||||
desc: '执行成功',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 50331,
|
||||
name: '合并代码',
|
||||
startTimeMillis: 1652343110691,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'build',
|
||||
desc: '执行成功',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 104692,
|
||||
name: '构建',
|
||||
startTimeMillis: 1652343177145,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'success',
|
||||
blank: true,
|
||||
linkUrl: 'https://devops.odboy.cn/depolyHistory/release?appName=kenaito-pilot&id=1',
|
||||
text: '查看部署详情',
|
||||
type: 'link'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doAgree?id=60012',
|
||||
text: '同意',
|
||||
type: 'execute'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doRefuse?id=60012',
|
||||
text: '拒绝',
|
||||
color: '#ff0000',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'deploy',
|
||||
desc: '等待部署审批',
|
||||
durationMillis: 161975,
|
||||
name: '部署',
|
||||
startTimeMillis: 1652343370745,
|
||||
status: 'running',
|
||||
statusDesc: '执行中'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/doCheck?id=60012',
|
||||
text: '确认合并',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'mergeMaster',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 7446,
|
||||
name: '合并主干',
|
||||
desc: '合并master确认',
|
||||
startTimeMillis: 1652343103245,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
}
|
||||
],
|
||||
task3Nodes: [
|
||||
{
|
||||
click: false,
|
||||
code: 'release',
|
||||
desc: '执行成功',
|
||||
durationMillis: 7446,
|
||||
name: '环境准备',
|
||||
startTimeMillis: 1652343103245,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'merge',
|
||||
desc: '执行失败',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 50331,
|
||||
name: '合并代码',
|
||||
startTimeMillis: 1652343110691,
|
||||
status: 'failed',
|
||||
statusDesc: '失败'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'build',
|
||||
desc: '待执行',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 0,
|
||||
name: '构建',
|
||||
startTimeMillis: 0,
|
||||
status: 'pending',
|
||||
statusDesc: '待执行'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'success',
|
||||
blank: true,
|
||||
linkUrl: 'https://devops.odboy.cn/depolyHistory/release?appName=kenaito-pilot&id=1',
|
||||
text: '查看部署详情',
|
||||
type: 'link'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doAgree?id=60012',
|
||||
text: '同意',
|
||||
type: 'execute'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doRefuse?id=60012',
|
||||
text: '拒绝',
|
||||
color: '#ff0000',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'deploy',
|
||||
desc: '待执行',
|
||||
durationMillis: 0,
|
||||
name: '部署',
|
||||
startTimeMillis: 0,
|
||||
status: 'pending',
|
||||
statusDesc: '待执行'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/doCheck?id=60012',
|
||||
text: '确认合并',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'mergeMaster',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 0,
|
||||
name: '合并主干',
|
||||
desc: '待执行',
|
||||
startTimeMillis: 0,
|
||||
status: 'pending',
|
||||
statusDesc: '待执行'
|
||||
}
|
||||
],
|
||||
task4Nodes: [
|
||||
{
|
||||
click: false,
|
||||
code: 'release',
|
||||
desc: '执行成功',
|
||||
durationMillis: 7446,
|
||||
name: '环境准备',
|
||||
startTimeMillis: 1652343103245,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'merge',
|
||||
desc: '执行成功',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 50331,
|
||||
name: '合并代码',
|
||||
startTimeMillis: 1652343110691,
|
||||
status: 'success',
|
||||
statusDesc: '成功'
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
retry: true,
|
||||
code: 'build',
|
||||
desc: '执行失败',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 50331,
|
||||
name: '构建',
|
||||
startTimeMillis: 1652343110691,
|
||||
status: 'failed',
|
||||
statusDesc: '失败'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'success',
|
||||
blank: true,
|
||||
linkUrl: 'https://devops.odboy.cn/depolyHistory/release?appName=kenaito-pilot&id=1',
|
||||
text: '查看部署详情',
|
||||
type: 'link'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doAgree?id=60012',
|
||||
text: '同意',
|
||||
type: 'execute'
|
||||
},
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/apply/doRefuse?id=60012',
|
||||
text: '拒绝',
|
||||
color: '#ff0000',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'deploy',
|
||||
desc: '待执行',
|
||||
durationMillis: 0,
|
||||
name: '部署',
|
||||
startTimeMillis: 0,
|
||||
status: 'pending',
|
||||
statusDesc: '待执行'
|
||||
},
|
||||
{
|
||||
buttonList: [
|
||||
{
|
||||
condition: 'running',
|
||||
method: 'get',
|
||||
requestUrl: 'api/devops/ci/doCheck?id=60012',
|
||||
text: '确认合并',
|
||||
type: 'execute'
|
||||
}
|
||||
],
|
||||
click: false,
|
||||
code: 'mergeMaster',
|
||||
detailType: 'gitlab',
|
||||
durationMillis: 0,
|
||||
name: '合并主干',
|
||||
desc: '待执行',
|
||||
startTimeMillis: 0,
|
||||
status: 'pending',
|
||||
statusDesc: '待执行'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package cn.odboy.constool;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.odboy.base.KitBaseUserTimeTb;
|
||||
import cn.odboy.framework.exception.BadRequestException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据对比同步工具
|
||||
* <p>
|
||||
* 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准:
|
||||
* <p>
|
||||
* - 本地不存在的数据 → 新增
|
||||
* <p>
|
||||
* - 本地存在的数据 → 更新
|
||||
* <p>
|
||||
* - 对照组不存在的数据 → 删除
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author odboy
|
||||
*/
|
||||
public class KitDiffSyncConstTool<T> {
|
||||
|
||||
/**
|
||||
* 数据同步回调接口
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public interface SyncCallback<T> {
|
||||
/**
|
||||
* 全量存储对照组数据
|
||||
*
|
||||
* @param records 需要保存的数据列表
|
||||
*/
|
||||
void doSaveBatch(List<T> records);
|
||||
|
||||
/**
|
||||
* 批量更新数据
|
||||
*
|
||||
* @param localList 本地已存在的数据列表
|
||||
* @param compareList 对照组对应的数据列表
|
||||
*/
|
||||
void doUpdateBatch(List<T> localList, List<T> compareList);
|
||||
|
||||
/**
|
||||
* 批量删除数据
|
||||
*
|
||||
* @param records 需要删除的数据列表
|
||||
*/
|
||||
void doDeleteBatch(List<T> records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地数据源
|
||||
*/
|
||||
private final List<T> localRecords;
|
||||
|
||||
/**
|
||||
* 对照数据源(以对照数据源为准)
|
||||
*/
|
||||
private final List<T> compareRecords;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param localRecords 本地数据源
|
||||
* @param compareRecords 对照数据源(基准数据)
|
||||
*/
|
||||
public KitDiffSyncConstTool(List<T> localRecords, List<T> compareRecords) {
|
||||
this.localRecords = localRecords;
|
||||
this.compareRecords = compareRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据对比同步
|
||||
*
|
||||
* @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId)
|
||||
* @param callback 同步回调接口,处理具体的增删改操作
|
||||
* @param <K> 唯一键类型
|
||||
*/
|
||||
public <K> void sync(Function<? super T, ? extends K> keyMapper, SyncCallback<T> callback) {
|
||||
if (keyMapper == null) {
|
||||
throw new BadRequestException("参数 keyMapper 必填");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new BadRequestException("参数 callback 必填");
|
||||
}
|
||||
|
||||
// 如果本地数据为空,直接全量保存对照数据
|
||||
if (this.localRecords.isEmpty()) {
|
||||
callback.doSaveBatch(this.compareRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建本地数据的键值映射
|
||||
Map<K, T> localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i));
|
||||
|
||||
List<T> insertList = new ArrayList<>();
|
||||
List<T> updateCompareList = new ArrayList<>();
|
||||
List<T> updateLocalList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 遍历对照数据,识别新增和更新
|
||||
for (T compareRecord : compareRecords) {
|
||||
K key = keyMapper.apply(compareRecord);
|
||||
T local = localMap.get(key);
|
||||
|
||||
if (local == null) {
|
||||
// 本地不存在,需要新增
|
||||
insertList.add(compareRecord);
|
||||
} else {
|
||||
// 本地已存在,需要更新
|
||||
updateLocalList.add(local);
|
||||
updateCompareList.add(compareRecord);
|
||||
// 标记为已处理
|
||||
localMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余的就是本地存在但对照组中不存在的,需要删除
|
||||
for (Map.Entry<K, T> entry : localMap.entrySet()) {
|
||||
deleteList.add(entry.getValue());
|
||||
}
|
||||
|
||||
// 执行批量操作
|
||||
if (CollUtil.isNotEmpty(insertList)) {
|
||||
callback.doSaveBatch(insertList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(updateLocalList)) {
|
||||
callback.doUpdateBatch(updateLocalList, updateCompareList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(deleteList)) {
|
||||
callback.doDeleteBatch(deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
List<KitBaseUserTimeTb> localList = new ArrayList<>();
|
||||
List<KitBaseUserTimeTb> onlineList = new ArrayList<>();
|
||||
|
||||
KitDiffSyncConstTool<KitBaseUserTimeTb> diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList);
|
||||
diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() {
|
||||
@Override
|
||||
public void doSaveBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 全量保存
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateBatch(List<KitBaseUserTimeTb> localList, List<KitBaseUserTimeTb> compareList) {
|
||||
// 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeleteBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 条件删除
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 存放常用的业务工具
|
||||
*/
|
||||
package cn.odboy.constool;
|
||||
+3
-3
@@ -27,7 +27,7 @@ import lombok.experimental.UtilityClass;
|
||||
* @author odboy
|
||||
*/
|
||||
@UtilityClass
|
||||
public class KitDynamicTableUtil {
|
||||
public class KitDynamicTableBuilder {
|
||||
|
||||
/**
|
||||
* 渲染排序规则
|
||||
@@ -37,7 +37,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> QueryWrapper<M> renderOrder(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> QueryWrapper<M> ofOrder(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = new QueryWrapper<>();
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
@@ -54,7 +54,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> LambdaQueryWrapper<M> renderAll(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> LambdaQueryWrapper<M> ofAll(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = KitMpQUtil.build(pageArgs.getArgs());
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
+98
-62
@@ -25,74 +25,110 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@UtilityClass
|
||||
public final class KitStringUtil {
|
||||
|
||||
private static final char SEPARATOR = '_';
|
||||
private static final char SEPARATOR = '_';
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节数
|
||||
*
|
||||
* @param storage 字节数
|
||||
* @return /
|
||||
*/
|
||||
private String renderBytes(Long storage) {
|
||||
if (storage == null || storage <= 0) {
|
||||
return "0 Byte";
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
double value = storage.doubleValue();
|
||||
final long KB = 1024L;
|
||||
final long MB = KB * 1024L;
|
||||
final long GB = MB * 1024L;
|
||||
final long TB = GB * 1024L;
|
||||
final long PB = TB * 1024L;
|
||||
|
||||
if (value >= PB) {
|
||||
return String.format("%.2f PB", value / PB);
|
||||
} else if (value >= TB) {
|
||||
return String.format("%.2f TB", value / TB);
|
||||
} else if (value >= GB) {
|
||||
return String.format("%.2f GB", value / GB);
|
||||
} else if (value >= MB) {
|
||||
return String.format("%.2f MB", value / MB);
|
||||
} else if (value >= KB) {
|
||||
return String.format("%.2f KB", value / KB);
|
||||
} else {
|
||||
return String.format("%d Byte", storage);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ package cn.odboy.system.features;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.odboy.base.KitPageArgs;
|
||||
import cn.odboy.feature.KitDynamicTableResponse;
|
||||
import cn.odboy.feature.KitDynamicTableUtil;
|
||||
import cn.odboy.feature.KitDynamicTableBuilder;
|
||||
import cn.odboy.system.dal.dataobject.SystemMenuTb;
|
||||
import cn.odboy.system.dal.model.request.SystemQueryMenuArgs;
|
||||
import cn.odboy.system.dal.mysql.SystemMenuMapper;
|
||||
@@ -75,7 +75,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu2(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染排序(非必须)
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderOrder(pageArgs);
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofOrder(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> lambda = wrapper.lambda();
|
||||
// 查询条件
|
||||
SystemQueryMenuArgs args = pageArgs.getArgs();
|
||||
@@ -91,7 +91,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu3(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染查询条件
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderAll(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofAll(pageArgs);
|
||||
// 响应结果
|
||||
return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper));
|
||||
}
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package cn.odboy.constool;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.odboy.base.KitBaseUserTimeTb;
|
||||
import cn.odboy.framework.exception.BadRequestException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据对比同步工具
|
||||
* <p>
|
||||
* 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准:
|
||||
* <p>
|
||||
* - 本地不存在的数据 → 新增
|
||||
* <p>
|
||||
* - 本地存在的数据 → 更新
|
||||
* <p>
|
||||
* - 对照组不存在的数据 → 删除
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author odboy
|
||||
*/
|
||||
public class KitDiffSyncConstTool<T> {
|
||||
|
||||
/**
|
||||
* 数据同步回调接口
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public interface SyncCallback<T> {
|
||||
/**
|
||||
* 全量存储对照组数据
|
||||
*
|
||||
* @param records 需要保存的数据列表
|
||||
*/
|
||||
void doSaveBatch(List<T> records);
|
||||
|
||||
/**
|
||||
* 批量更新数据
|
||||
*
|
||||
* @param localList 本地已存在的数据列表
|
||||
* @param compareList 对照组对应的数据列表
|
||||
*/
|
||||
void doUpdateBatch(List<T> localList, List<T> compareList);
|
||||
|
||||
/**
|
||||
* 批量删除数据
|
||||
*
|
||||
* @param records 需要删除的数据列表
|
||||
*/
|
||||
void doDeleteBatch(List<T> records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地数据源
|
||||
*/
|
||||
private final List<T> localRecords;
|
||||
|
||||
/**
|
||||
* 对照数据源(以对照数据源为准)
|
||||
*/
|
||||
private final List<T> compareRecords;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param localRecords 本地数据源
|
||||
* @param compareRecords 对照数据源(基准数据)
|
||||
*/
|
||||
public KitDiffSyncConstTool(List<T> localRecords, List<T> compareRecords) {
|
||||
this.localRecords = localRecords;
|
||||
this.compareRecords = compareRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据对比同步
|
||||
*
|
||||
* @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId)
|
||||
* @param callback 同步回调接口,处理具体的增删改操作
|
||||
* @param <K> 唯一键类型
|
||||
*/
|
||||
public <K> void sync(Function<? super T, ? extends K> keyMapper, SyncCallback<T> callback) {
|
||||
if (keyMapper == null) {
|
||||
throw new BadRequestException("参数 keyMapper 必填");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new BadRequestException("参数 callback 必填");
|
||||
}
|
||||
|
||||
// 如果本地数据为空,直接全量保存对照数据
|
||||
if (this.localRecords.isEmpty()) {
|
||||
callback.doSaveBatch(this.compareRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建本地数据的键值映射
|
||||
Map<K, T> localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i));
|
||||
|
||||
List<T> insertList = new ArrayList<>();
|
||||
List<T> updateCompareList = new ArrayList<>();
|
||||
List<T> updateLocalList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 遍历对照数据,识别新增和更新
|
||||
for (T compareRecord : compareRecords) {
|
||||
K key = keyMapper.apply(compareRecord);
|
||||
T local = localMap.get(key);
|
||||
|
||||
if (local == null) {
|
||||
// 本地不存在,需要新增
|
||||
insertList.add(compareRecord);
|
||||
} else {
|
||||
// 本地已存在,需要更新
|
||||
updateLocalList.add(local);
|
||||
updateCompareList.add(compareRecord);
|
||||
// 标记为已处理
|
||||
localMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余的就是本地存在但对照组中不存在的,需要删除
|
||||
for (Map.Entry<K, T> entry : localMap.entrySet()) {
|
||||
deleteList.add(entry.getValue());
|
||||
}
|
||||
|
||||
// 执行批量操作
|
||||
if (CollUtil.isNotEmpty(insertList)) {
|
||||
callback.doSaveBatch(insertList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(updateLocalList)) {
|
||||
callback.doUpdateBatch(updateLocalList, updateCompareList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(deleteList)) {
|
||||
callback.doDeleteBatch(deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
List<KitBaseUserTimeTb> localList = new ArrayList<>();
|
||||
List<KitBaseUserTimeTb> onlineList = new ArrayList<>();
|
||||
|
||||
KitDiffSyncConstTool<KitBaseUserTimeTb> diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList);
|
||||
diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() {
|
||||
@Override
|
||||
public void doSaveBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 全量保存
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateBatch(List<KitBaseUserTimeTb> localList, List<KitBaseUserTimeTb> compareList) {
|
||||
// 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeleteBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 条件删除
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 存放常用的业务工具
|
||||
*/
|
||||
package cn.odboy.constool;
|
||||
+3
-3
@@ -27,7 +27,7 @@ import lombok.experimental.UtilityClass;
|
||||
* @author odboy
|
||||
*/
|
||||
@UtilityClass
|
||||
public class KitDynamicTableUtil {
|
||||
public class KitDynamicTableBuilder {
|
||||
|
||||
/**
|
||||
* 渲染排序规则
|
||||
@@ -37,7 +37,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> QueryWrapper<M> renderOrder(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> QueryWrapper<M> ofOrder(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = new QueryWrapper<>();
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
@@ -54,7 +54,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> LambdaQueryWrapper<M> renderAll(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> LambdaQueryWrapper<M> ofAll(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = KitMpQUtil.build(pageArgs.getArgs());
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
+98
-62
@@ -25,74 +25,110 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@UtilityClass
|
||||
public final class KitStringUtil {
|
||||
|
||||
private static final char SEPARATOR = '_';
|
||||
private static final char SEPARATOR = '_';
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节数
|
||||
*
|
||||
* @param storage 字节数
|
||||
* @return /
|
||||
*/
|
||||
private String renderBytes(Long storage) {
|
||||
if (storage == null || storage <= 0) {
|
||||
return "0 Byte";
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
double value = storage.doubleValue();
|
||||
final long KB = 1024L;
|
||||
final long MB = KB * 1024L;
|
||||
final long GB = MB * 1024L;
|
||||
final long TB = GB * 1024L;
|
||||
final long PB = TB * 1024L;
|
||||
|
||||
if (value >= PB) {
|
||||
return String.format("%.2f PB", value / PB);
|
||||
} else if (value >= TB) {
|
||||
return String.format("%.2f TB", value / TB);
|
||||
} else if (value >= GB) {
|
||||
return String.format("%.2f GB", value / GB);
|
||||
} else if (value >= MB) {
|
||||
return String.format("%.2f MB", value / MB);
|
||||
} else if (value >= KB) {
|
||||
return String.format("%.2f KB", value / KB);
|
||||
} else {
|
||||
return String.format("%d Byte", storage);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ package cn.odboy.system.features;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.odboy.base.KitPageArgs;
|
||||
import cn.odboy.feature.KitDynamicTableResponse;
|
||||
import cn.odboy.feature.KitDynamicTableUtil;
|
||||
import cn.odboy.feature.KitDynamicTableBuilder;
|
||||
import cn.odboy.system.dal.dataobject.SystemMenuTb;
|
||||
import cn.odboy.system.dal.model.request.SystemQueryMenuArgs;
|
||||
import cn.odboy.system.dal.mysql.SystemMenuMapper;
|
||||
@@ -75,7 +75,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu2(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染排序(非必须)
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderOrder(pageArgs);
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofOrder(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> lambda = wrapper.lambda();
|
||||
// 查询条件
|
||||
SystemQueryMenuArgs args = pageArgs.getArgs();
|
||||
@@ -91,7 +91,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu3(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染查询条件
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderAll(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofAll(pageArgs);
|
||||
// 响应结果
|
||||
return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper));
|
||||
}
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package cn.odboy.constool;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.odboy.base.KitBaseUserTimeTb;
|
||||
import cn.odboy.framework.exception.BadRequestException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据对比同步工具
|
||||
* <p>
|
||||
* 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准:
|
||||
* <p>
|
||||
* - 本地不存在的数据 → 新增
|
||||
* <p>
|
||||
* - 本地存在的数据 → 更新
|
||||
* <p>
|
||||
* - 对照组不存在的数据 → 删除
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author odboy
|
||||
*/
|
||||
public class KitDiffSyncConstTool<T> {
|
||||
|
||||
/**
|
||||
* 数据同步回调接口
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public interface SyncCallback<T> {
|
||||
/**
|
||||
* 全量存储对照组数据
|
||||
*
|
||||
* @param records 需要保存的数据列表
|
||||
*/
|
||||
void doSaveBatch(List<T> records);
|
||||
|
||||
/**
|
||||
* 批量更新数据
|
||||
*
|
||||
* @param localList 本地已存在的数据列表
|
||||
* @param compareList 对照组对应的数据列表
|
||||
*/
|
||||
void doUpdateBatch(List<T> localList, List<T> compareList);
|
||||
|
||||
/**
|
||||
* 批量删除数据
|
||||
*
|
||||
* @param records 需要删除的数据列表
|
||||
*/
|
||||
void doDeleteBatch(List<T> records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地数据源
|
||||
*/
|
||||
private final List<T> localRecords;
|
||||
|
||||
/**
|
||||
* 对照数据源(以对照数据源为准)
|
||||
*/
|
||||
private final List<T> compareRecords;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param localRecords 本地数据源
|
||||
* @param compareRecords 对照数据源(基准数据)
|
||||
*/
|
||||
public KitDiffSyncConstTool(List<T> localRecords, List<T> compareRecords) {
|
||||
this.localRecords = localRecords;
|
||||
this.compareRecords = compareRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据对比同步
|
||||
*
|
||||
* @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId)
|
||||
* @param callback 同步回调接口,处理具体的增删改操作
|
||||
* @param <K> 唯一键类型
|
||||
*/
|
||||
public <K> void sync(Function<? super T, ? extends K> keyMapper, SyncCallback<T> callback) {
|
||||
if (keyMapper == null) {
|
||||
throw new BadRequestException("参数 keyMapper 必填");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new BadRequestException("参数 callback 必填");
|
||||
}
|
||||
|
||||
// 如果本地数据为空,直接全量保存对照数据
|
||||
if (this.localRecords.isEmpty()) {
|
||||
callback.doSaveBatch(this.compareRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建本地数据的键值映射
|
||||
Map<K, T> localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i));
|
||||
|
||||
List<T> insertList = new ArrayList<>();
|
||||
List<T> updateCompareList = new ArrayList<>();
|
||||
List<T> updateLocalList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 遍历对照数据,识别新增和更新
|
||||
for (T compareRecord : compareRecords) {
|
||||
K key = keyMapper.apply(compareRecord);
|
||||
T local = localMap.get(key);
|
||||
|
||||
if (local == null) {
|
||||
// 本地不存在,需要新增
|
||||
insertList.add(compareRecord);
|
||||
} else {
|
||||
// 本地已存在,需要更新
|
||||
updateLocalList.add(local);
|
||||
updateCompareList.add(compareRecord);
|
||||
// 标记为已处理
|
||||
localMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余的就是本地存在但对照组中不存在的,需要删除
|
||||
for (Map.Entry<K, T> entry : localMap.entrySet()) {
|
||||
deleteList.add(entry.getValue());
|
||||
}
|
||||
|
||||
// 执行批量操作
|
||||
if (CollUtil.isNotEmpty(insertList)) {
|
||||
callback.doSaveBatch(insertList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(updateLocalList)) {
|
||||
callback.doUpdateBatch(updateLocalList, updateCompareList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(deleteList)) {
|
||||
callback.doDeleteBatch(deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
List<KitBaseUserTimeTb> localList = new ArrayList<>();
|
||||
List<KitBaseUserTimeTb> onlineList = new ArrayList<>();
|
||||
|
||||
KitDiffSyncConstTool<KitBaseUserTimeTb> diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList);
|
||||
diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() {
|
||||
@Override
|
||||
public void doSaveBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 全量保存
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateBatch(List<KitBaseUserTimeTb> localList, List<KitBaseUserTimeTb> compareList) {
|
||||
// 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeleteBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 条件删除
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 存放常用的业务工具
|
||||
*/
|
||||
package cn.odboy.constool;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2021-2026 Odboy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.odboy.feature;
|
||||
|
||||
import cn.odboy.base.KitPageArgs;
|
||||
import cn.odboy.framework.mybatisplus.core.KitMpQUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* 动态表格辅助工具
|
||||
*
|
||||
* @author odboy
|
||||
*/
|
||||
@UtilityClass
|
||||
public class KitDynamicTableBuilder {
|
||||
|
||||
/**
|
||||
* 渲染排序规则
|
||||
*
|
||||
* @param pageArgs 分页参数
|
||||
* @param <M> 数据表对象
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> QueryWrapper<M> ofOrder(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = new QueryWrapper<>();
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn());
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有规则(需要配合 @KitMpQuery 注解使用)
|
||||
*
|
||||
* @param pageArgs 分页参数
|
||||
* @param <M> 数据表对象
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> LambdaQueryWrapper<M> ofAll(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = KitMpQUtil.build(pageArgs.getArgs());
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn());
|
||||
}
|
||||
return wrapper.lambda();
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2026 Odboy
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.odboy.feature;
|
||||
|
||||
import cn.odboy.base.KitPageArgs;
|
||||
import cn.odboy.framework.mybatisplus.core.KitMpQUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
/**
|
||||
* 动态表格辅助工具
|
||||
*
|
||||
* @author odboy
|
||||
*/
|
||||
@UtilityClass
|
||||
public class KitDynamicTableUtil {
|
||||
|
||||
/**
|
||||
* 渲染排序规则
|
||||
*
|
||||
* @param pageArgs 分页参数
|
||||
* @param <M> 数据表对象
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> QueryWrapper<M> renderOrder(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = new QueryWrapper<>();
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn());
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染所有规则(需要配合 @KitMpQuery 注解使用)
|
||||
*
|
||||
* @param pageArgs 分页参数
|
||||
* @param <M> 数据表对象
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> LambdaQueryWrapper<M> renderAll(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = KitMpQUtil.build(pageArgs.getArgs());
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn());
|
||||
}
|
||||
return wrapper.lambda();
|
||||
}
|
||||
}
|
||||
@@ -25,74 +25,110 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@UtilityClass
|
||||
public final class KitStringUtil {
|
||||
|
||||
private static final char SEPARATOR = '_';
|
||||
private static final char SEPARATOR = '_';
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节数
|
||||
*
|
||||
* @param storage 字节数
|
||||
* @return /
|
||||
*/
|
||||
private String renderBytes(Long storage) {
|
||||
if (storage == null || storage <= 0) {
|
||||
return "0 Byte";
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
double value = storage.doubleValue();
|
||||
final long KB = 1024L;
|
||||
final long MB = KB * 1024L;
|
||||
final long GB = MB * 1024L;
|
||||
final long TB = GB * 1024L;
|
||||
final long PB = TB * 1024L;
|
||||
|
||||
if (value >= PB) {
|
||||
return String.format("%.2f PB", value / PB);
|
||||
} else if (value >= TB) {
|
||||
return String.format("%.2f TB", value / TB);
|
||||
} else if (value >= GB) {
|
||||
return String.format("%.2f GB", value / GB);
|
||||
} else if (value >= MB) {
|
||||
return String.format("%.2f MB", value / MB);
|
||||
} else if (value >= KB) {
|
||||
return String.format("%.2f KB", value / KB);
|
||||
} else {
|
||||
return String.format("%d Byte", storage);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ package cn.odboy.system.features;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.odboy.base.KitPageArgs;
|
||||
import cn.odboy.feature.KitDynamicTableResponse;
|
||||
import cn.odboy.feature.KitDynamicTableUtil;
|
||||
import cn.odboy.feature.KitDynamicTableBuilder;
|
||||
import cn.odboy.system.dal.dataobject.SystemMenuTb;
|
||||
import cn.odboy.system.dal.model.request.SystemQueryMenuArgs;
|
||||
import cn.odboy.system.dal.mysql.SystemMenuMapper;
|
||||
@@ -81,7 +81,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu2(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染排序(非必须)
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderOrder(pageArgs);
|
||||
QueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofOrder(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> lambda = wrapper.lambda();
|
||||
// 查询条件
|
||||
SystemQueryMenuArgs args = pageArgs.getArgs();
|
||||
@@ -98,7 +98,7 @@ public class CuteDynamicTableDemoApi {
|
||||
@PreAuthorize("@el.check()")
|
||||
public ResponseEntity<KitDynamicTableResponse<SystemMenuTb, SystemMenuDynamicTableModel>> searchMenu3(@Validated @RequestBody KitPageArgs<SystemQueryMenuArgs> pageArgs) {
|
||||
// 渲染查询条件
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableUtil.renderAll(pageArgs);
|
||||
LambdaQueryWrapper<SystemMenuTb> wrapper = KitDynamicTableBuilder.ofAll(pageArgs);
|
||||
// 响应结果
|
||||
return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.odboy;
|
||||
|
||||
import cn.odboy.system.dal.dataobject.SystemDeptTb;
|
||||
import cn.odboy.util.KitClassUtil;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Mybatis批量操作生成器
|
||||
*
|
||||
* @author odboy
|
||||
*/
|
||||
public class MybatisMapperBatchOpRender {
|
||||
/**
|
||||
* 持久类名
|
||||
*/
|
||||
private String entityClassName;
|
||||
/**
|
||||
* 持久类主键类型名
|
||||
*/
|
||||
private String entityPkTypeName;
|
||||
|
||||
/**
|
||||
* 渲染MapperXml
|
||||
*
|
||||
* @param entityClass 持久类
|
||||
* @param keyMapper 哪个是主键
|
||||
*/
|
||||
public <T, K> void render(Class<?> entityClass, Function<? super T, ? extends K> keyMapper) {
|
||||
String mapperBatchInsert = "void batchInsert(@Param(\"list\") List<$EntityClassName> list);";
|
||||
System.err.println(mapperBatchInsert.replace("$EntityClassName", this.entityClassName));
|
||||
String mapperBatchUpdate = "void batchUpdateById(@Param(\"list\") List<$EntityClassName> list);";
|
||||
System.err.println(mapperBatchUpdate.replace("$EntityClassName", this.entityClassName));
|
||||
String mapperBatchDelete = "void batchDeleteByIds(@Param(\"list\") List<$EntityPkTypeName> list);";
|
||||
System.err.println(mapperBatchDelete.replace("$EntityPkTypeName", this.entityPkTypeName));
|
||||
|
||||
List<Field> fields = new ArrayList<>();
|
||||
KitClassUtil.getAllFields(entityClass, fields);
|
||||
|
||||
fields = fields.stream().filter(f -> !"serialVersionUID".equals(f.getName())).collect(Collectors.toList());
|
||||
|
||||
System.err.println(fields);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
new MybatisMapperBatchOpRender().render(SystemDeptTb.class, SystemDeptTb::getId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user