From a14809e20357cf2ffa2f57c7566ce0a4fac589ba Mon Sep 17 00:00:00 2001 From: Odboy Date: Fri, 6 Mar 2026 19:19:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(constool):=20=E6=B7=BB=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=AF=B9=E6=AF=94=E5=90=8C=E6=AD=A5=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=B9=B6=E9=87=8D=E6=9E=84=E5=8A=A8=E6=80=81=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 KitDiffSyncConstTool 工具类,支持集合数据的对比同步功能 - 添加数据同步回调接口和完整的增删改查批量操作支持 - 重命名 KitDynamicTableUtil 为 KitDynamicTableBuilder 并调整方法名称 - 修改 renderOrder 方法为 ofOrder,renderAll 方法为 ofAll - 更新前端 FormatDuration 函数,支持年、天、时、分、秒格式化显示 - 添加字节格式化方法 renderBytes 到 KitStringUtil 工具类 - 在首页视图中集成 Pipeline 组件用于展示任务流程 - 添加窗口打开工具函数 OpenWindowInCurrent 和 OpenWindowInNew - 创建 MybatisMapperBatchOpRender 批量操作生成器 --- cutejava-front/src/utils/KitUtil.js | 8 + cutejava-front/src/utils/filters.js | 23 +- .../components/devops/pipeline/PipeTask.vue | 317 ++++++++++++++ .../components/devops/pipeline/Pipeline.vue | 70 +++ cutejava-front/src/views/home.vue | 399 +++++++++++++++++- .../odboy/constool/KitDiffSyncConstTool.java | 168 ++++++++ .../java/cn/odboy/constool/package-info.java | 4 + ...eUtil.java => KitDynamicTableBuilder.java} | 6 +- .../java/cn/odboy/util/KitStringUtil.java | 160 ++++--- .../features/CuteDynamicTableDemoApi.java | 6 +- .../odboy/constool/KitDiffSyncConstTool.java | 168 ++++++++ .../java/cn/odboy/constool/package-info.java | 4 + ...eUtil.java => KitDynamicTableBuilder.java} | 6 +- .../java/cn/odboy/util/KitStringUtil.java | 160 ++++--- .../features/CuteDynamicTableDemoApi.java | 6 +- .../odboy/constool/KitDiffSyncConstTool.java | 168 ++++++++ .../java/cn/odboy/constool/package-info.java | 4 + .../odboy/feature/KitDynamicTableBuilder.java | 65 +++ .../cn/odboy/feature/KitDynamicTableUtil.java | 65 --- .../java/cn/odboy/util/KitStringUtil.java | 160 ++++--- .../features/CuteDynamicTableDemoApi.java | 6 +- .../cn/odboy/MybatisMapperBatchOpRender.java | 51 +++ 22 files changed, 1749 insertions(+), 275 deletions(-) create mode 100644 cutejava-front/src/views/components/devops/pipeline/PipeTask.vue create mode 100644 cutejava-front/src/views/components/devops/pipeline/Pipeline.vue create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java create mode 100644 cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java rename cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/{KitDynamicTableUtil.java => KitDynamicTableBuilder.java} (91%) create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java create mode 100644 cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java rename cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/{KitDynamicTableUtil.java => KitDynamicTableBuilder.java} (91%) create mode 100644 cutejava/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java create mode 100644 cutejava/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java create mode 100644 cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java delete mode 100644 cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java create mode 100644 cutejava/cutejava-starter/src/main/java/cn/odboy/MybatisMapperBatchOpRender.java diff --git a/cutejava-front/src/utils/KitUtil.js b/cutejava-front/src/utils/KitUtil.js index 6c62e35d..b5a7e516 100644 --- a/cutejava-front/src/utils/KitUtil.js +++ b/cutejava-front/src/utils/KitUtil.js @@ -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') +} diff --git a/cutejava-front/src/utils/filters.js b/cutejava-front/src/utils/filters.js index c6132228..ddb70625 100644 --- a/cutejava-front/src/utils/filters.js +++ b/cutejava-front/src/utils/filters.js @@ -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}秒` } } diff --git a/cutejava-front/src/views/components/devops/pipeline/PipeTask.vue b/cutejava-front/src/views/components/devops/pipeline/PipeTask.vue new file mode 100644 index 00000000..6ffa8f12 --- /dev/null +++ b/cutejava-front/src/views/components/devops/pipeline/PipeTask.vue @@ -0,0 +1,317 @@ + + + + diff --git a/cutejava-front/src/views/components/devops/pipeline/Pipeline.vue b/cutejava-front/src/views/components/devops/pipeline/Pipeline.vue new file mode 100644 index 00000000..e11b511a --- /dev/null +++ b/cutejava-front/src/views/components/devops/pipeline/Pipeline.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/cutejava-front/src/views/home.vue b/cutejava-front/src/views/home.vue index 98eede86..5e533a90 100644 --- a/cutejava-front/src/views/home.vue +++ b/cutejava-front/src/views/home.vue @@ -3,11 +3,20 @@ - 系统API文档:{{ swaggerApi }} + 系统API文档: + {{ swaggerApi }} + +
+ + + + +
+ @@ -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() { diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java new file mode 100644 index 00000000..82b526e3 --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java @@ -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; + +/** + * 数据对比同步工具 + *

+ * 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准: + *

+ * - 本地不存在的数据 → 新增 + *

+ * - 本地存在的数据 → 更新 + *

+ * - 对照组不存在的数据 → 删除 + * + * @param 数据类型 + * @author odboy + */ +public class KitDiffSyncConstTool { + + /** + * 数据同步回调接口 + * + * @param 数据类型 + */ + public interface SyncCallback { + /** + * 全量存储对照组数据 + * + * @param records 需要保存的数据列表 + */ + void doSaveBatch(List records); + + /** + * 批量更新数据 + * + * @param localList 本地已存在的数据列表 + * @param compareList 对照组对应的数据列表 + */ + void doUpdateBatch(List localList, List compareList); + + /** + * 批量删除数据 + * + * @param records 需要删除的数据列表 + */ + void doDeleteBatch(List records); + } + + /** + * 本地数据源 + */ + private final List localRecords; + + /** + * 对照数据源(以对照数据源为准) + */ + private final List compareRecords; + + /** + * 构造函数 + * + * @param localRecords 本地数据源 + * @param compareRecords 对照数据源(基准数据) + */ + public KitDiffSyncConstTool(List localRecords, List compareRecords) { + this.localRecords = localRecords; + this.compareRecords = compareRecords; + } + + /** + * 执行数据对比同步 + * + * @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId) + * @param callback 同步回调接口,处理具体的增删改操作 + * @param 唯一键类型 + */ + public void sync(Function keyMapper, SyncCallback 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 localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i)); + + List insertList = new ArrayList<>(); + List updateCompareList = new ArrayList<>(); + List updateLocalList = new ArrayList<>(); + List 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 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 localList = new ArrayList<>(); + List onlineList = new ArrayList<>(); + + KitDiffSyncConstTool diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList); + diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() { + @Override + public void doSaveBatch(List records) { + // 全量保存 + } + + @Override + public void doUpdateBatch(List localList, List compareList) { + // 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据 + } + + @Override + public void doDeleteBatch(List records) { + // 条件删除 + } + }); + } +} diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java new file mode 100644 index 00000000..4ce4928e --- /dev/null +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java @@ -0,0 +1,4 @@ +/** + * 存放常用的业务工具 + */ +package cn.odboy.constool; diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java similarity index 91% rename from cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java rename to cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java index 9f33d1d4..cbfa152b 100644 --- a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java @@ -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 渲染对象 * @return / */ - public static QueryWrapper renderOrder(KitPageArgs pageArgs) { + public static QueryWrapper ofOrder(KitPageArgs pageArgs) { QueryWrapper wrapper = new QueryWrapper<>(); KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); if (orderBy != null) { @@ -54,7 +54,7 @@ public class KitDynamicTableUtil { * @param 渲染对象 * @return / */ - public static LambdaQueryWrapper renderAll(KitPageArgs pageArgs) { + public static LambdaQueryWrapper ofAll(KitPageArgs pageArgs) { QueryWrapper wrapper = KitMpQUtil.build(pageArgs.getArgs()); KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); if (orderBy != null) { diff --git a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java index 3f5e01dd..5a14e916 100644 --- a/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java +++ b/cutejava-springboot3/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java @@ -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(); - } } diff --git a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java index d986fe8e..ea877ba0 100644 --- a/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java +++ b/cutejava-springboot3/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java @@ -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> searchMenu2(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染排序(非必须) - QueryWrapper wrapper = KitDynamicTableUtil.renderOrder(pageArgs); + QueryWrapper wrapper = KitDynamicTableBuilder.ofOrder(pageArgs); LambdaQueryWrapper lambda = wrapper.lambda(); // 查询条件 SystemQueryMenuArgs args = pageArgs.getArgs(); @@ -91,7 +91,7 @@ public class CuteDynamicTableDemoApi { @PreAuthorize("@el.check()") public ResponseEntity> searchMenu3(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染查询条件 - LambdaQueryWrapper wrapper = KitDynamicTableUtil.renderAll(pageArgs); + LambdaQueryWrapper wrapper = KitDynamicTableBuilder.ofAll(pageArgs); // 响应结果 return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper)); } diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java new file mode 100644 index 00000000..82b526e3 --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java @@ -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; + +/** + * 数据对比同步工具 + *

+ * 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准: + *

+ * - 本地不存在的数据 → 新增 + *

+ * - 本地存在的数据 → 更新 + *

+ * - 对照组不存在的数据 → 删除 + * + * @param 数据类型 + * @author odboy + */ +public class KitDiffSyncConstTool { + + /** + * 数据同步回调接口 + * + * @param 数据类型 + */ + public interface SyncCallback { + /** + * 全量存储对照组数据 + * + * @param records 需要保存的数据列表 + */ + void doSaveBatch(List records); + + /** + * 批量更新数据 + * + * @param localList 本地已存在的数据列表 + * @param compareList 对照组对应的数据列表 + */ + void doUpdateBatch(List localList, List compareList); + + /** + * 批量删除数据 + * + * @param records 需要删除的数据列表 + */ + void doDeleteBatch(List records); + } + + /** + * 本地数据源 + */ + private final List localRecords; + + /** + * 对照数据源(以对照数据源为准) + */ + private final List compareRecords; + + /** + * 构造函数 + * + * @param localRecords 本地数据源 + * @param compareRecords 对照数据源(基准数据) + */ + public KitDiffSyncConstTool(List localRecords, List compareRecords) { + this.localRecords = localRecords; + this.compareRecords = compareRecords; + } + + /** + * 执行数据对比同步 + * + * @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId) + * @param callback 同步回调接口,处理具体的增删改操作 + * @param 唯一键类型 + */ + public void sync(Function keyMapper, SyncCallback 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 localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i)); + + List insertList = new ArrayList<>(); + List updateCompareList = new ArrayList<>(); + List updateLocalList = new ArrayList<>(); + List 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 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 localList = new ArrayList<>(); + List onlineList = new ArrayList<>(); + + KitDiffSyncConstTool diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList); + diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() { + @Override + public void doSaveBatch(List records) { + // 全量保存 + } + + @Override + public void doUpdateBatch(List localList, List compareList) { + // 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据 + } + + @Override + public void doDeleteBatch(List records) { + // 条件删除 + } + }); + } +} diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java new file mode 100644 index 00000000..4ce4928e --- /dev/null +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java @@ -0,0 +1,4 @@ +/** + * 存放常用的业务工具 + */ +package cn.odboy.constool; diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java similarity index 91% rename from cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java rename to cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java index 9f33d1d4..cbfa152b 100644 --- a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java @@ -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 渲染对象 * @return / */ - public static QueryWrapper renderOrder(KitPageArgs pageArgs) { + public static QueryWrapper ofOrder(KitPageArgs pageArgs) { QueryWrapper wrapper = new QueryWrapper<>(); KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); if (orderBy != null) { @@ -54,7 +54,7 @@ public class KitDynamicTableUtil { * @param 渲染对象 * @return / */ - public static LambdaQueryWrapper renderAll(KitPageArgs pageArgs) { + public static LambdaQueryWrapper ofAll(KitPageArgs pageArgs) { QueryWrapper wrapper = KitMpQUtil.build(pageArgs.getArgs()); KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); if (orderBy != null) { diff --git a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java index 3f5e01dd..5a14e916 100644 --- a/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java +++ b/cutejava-springboot4/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java @@ -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(); - } } diff --git a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java index d986fe8e..ea877ba0 100644 --- a/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java +++ b/cutejava-springboot4/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java @@ -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> searchMenu2(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染排序(非必须) - QueryWrapper wrapper = KitDynamicTableUtil.renderOrder(pageArgs); + QueryWrapper wrapper = KitDynamicTableBuilder.ofOrder(pageArgs); LambdaQueryWrapper lambda = wrapper.lambda(); // 查询条件 SystemQueryMenuArgs args = pageArgs.getArgs(); @@ -91,7 +91,7 @@ public class CuteDynamicTableDemoApi { @PreAuthorize("@el.check()") public ResponseEntity> searchMenu3(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染查询条件 - LambdaQueryWrapper wrapper = KitDynamicTableUtil.renderAll(pageArgs); + LambdaQueryWrapper wrapper = KitDynamicTableBuilder.ofAll(pageArgs); // 响应结果 return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper)); } diff --git a/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java b/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java new file mode 100644 index 00000000..82b526e3 --- /dev/null +++ b/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/KitDiffSyncConstTool.java @@ -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; + +/** + * 数据对比同步工具 + *

+ * 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准: + *

+ * - 本地不存在的数据 → 新增 + *

+ * - 本地存在的数据 → 更新 + *

+ * - 对照组不存在的数据 → 删除 + * + * @param 数据类型 + * @author odboy + */ +public class KitDiffSyncConstTool { + + /** + * 数据同步回调接口 + * + * @param 数据类型 + */ + public interface SyncCallback { + /** + * 全量存储对照组数据 + * + * @param records 需要保存的数据列表 + */ + void doSaveBatch(List records); + + /** + * 批量更新数据 + * + * @param localList 本地已存在的数据列表 + * @param compareList 对照组对应的数据列表 + */ + void doUpdateBatch(List localList, List compareList); + + /** + * 批量删除数据 + * + * @param records 需要删除的数据列表 + */ + void doDeleteBatch(List records); + } + + /** + * 本地数据源 + */ + private final List localRecords; + + /** + * 对照数据源(以对照数据源为准) + */ + private final List compareRecords; + + /** + * 构造函数 + * + * @param localRecords 本地数据源 + * @param compareRecords 对照数据源(基准数据) + */ + public KitDiffSyncConstTool(List localRecords, List compareRecords) { + this.localRecords = localRecords; + this.compareRecords = compareRecords; + } + + /** + * 执行数据对比同步 + * + * @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId) + * @param callback 同步回调接口,处理具体的增删改操作 + * @param 唯一键类型 + */ + public void sync(Function keyMapper, SyncCallback 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 localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i)); + + List insertList = new ArrayList<>(); + List updateCompareList = new ArrayList<>(); + List updateLocalList = new ArrayList<>(); + List 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 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 localList = new ArrayList<>(); + List onlineList = new ArrayList<>(); + + KitDiffSyncConstTool diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList); + diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() { + @Override + public void doSaveBatch(List records) { + // 全量保存 + } + + @Override + public void doUpdateBatch(List localList, List compareList) { + // 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据 + } + + @Override + public void doDeleteBatch(List records) { + // 条件删除 + } + }); + } +} diff --git a/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java b/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java new file mode 100644 index 00000000..4ce4928e --- /dev/null +++ b/cutejava/cutejava-framework/src/main/java/cn/odboy/constool/package-info.java @@ -0,0 +1,4 @@ +/** + * 存放常用的业务工具 + */ +package cn.odboy.constool; diff --git a/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java b/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java new file mode 100644 index 00000000..08391d17 --- /dev/null +++ b/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableBuilder.java @@ -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 数据表对象 + * @param 渲染对象 + * @return / + */ + public static QueryWrapper ofOrder(KitPageArgs pageArgs) { + QueryWrapper wrapper = new QueryWrapper<>(); + KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); + if (orderBy != null) { + wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); + } + return wrapper; + } + + /** + * 渲染所有规则(需要配合 @KitMpQuery 注解使用) + * + * @param pageArgs 分页参数 + * @param 数据表对象 + * @param 渲染对象 + * @return / + */ + public static LambdaQueryWrapper ofAll(KitPageArgs pageArgs) { + QueryWrapper wrapper = KitMpQUtil.build(pageArgs.getArgs()); + KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); + if (orderBy != null) { + wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); + } + return wrapper.lambda(); + } +} diff --git a/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java b/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java deleted file mode 100644 index 9f33d1d4..00000000 --- a/cutejava/cutejava-framework/src/main/java/cn/odboy/feature/KitDynamicTableUtil.java +++ /dev/null @@ -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 数据表对象 - * @param 渲染对象 - * @return / - */ - public static QueryWrapper renderOrder(KitPageArgs pageArgs) { - QueryWrapper wrapper = new QueryWrapper<>(); - KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); - if (orderBy != null) { - wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); - } - return wrapper; - } - - /** - * 渲染所有规则(需要配合 @KitMpQuery 注解使用) - * - * @param pageArgs 分页参数 - * @param 数据表对象 - * @param 渲染对象 - * @return / - */ - public static LambdaQueryWrapper renderAll(KitPageArgs pageArgs) { - QueryWrapper wrapper = KitMpQUtil.build(pageArgs.getArgs()); - KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy(); - if (orderBy != null) { - wrapper.orderBy(true, orderBy.isAsc(), orderBy.getColumn()); - } - return wrapper.lambda(); - } -} diff --git a/cutejava/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java b/cutejava/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java index 3f5e01dd..5a14e916 100644 --- a/cutejava/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java +++ b/cutejava/cutejava-framework/src/main/java/cn/odboy/util/KitStringUtil.java @@ -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(); - } } diff --git a/cutejava/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java b/cutejava/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java index 00225eb3..e539051d 100644 --- a/cutejava/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java +++ b/cutejava/cutejava-module-system/src/main/java/cn/odboy/system/features/CuteDynamicTableDemoApi.java @@ -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> searchMenu2(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染排序(非必须) - QueryWrapper wrapper = KitDynamicTableUtil.renderOrder(pageArgs); + QueryWrapper wrapper = KitDynamicTableBuilder.ofOrder(pageArgs); LambdaQueryWrapper lambda = wrapper.lambda(); // 查询条件 SystemQueryMenuArgs args = pageArgs.getArgs(); @@ -98,7 +98,7 @@ public class CuteDynamicTableDemoApi { @PreAuthorize("@el.check()") public ResponseEntity> searchMenu3(@Validated @RequestBody KitPageArgs pageArgs) { // 渲染查询条件 - LambdaQueryWrapper wrapper = KitDynamicTableUtil.renderAll(pageArgs); + LambdaQueryWrapper wrapper = KitDynamicTableBuilder.ofAll(pageArgs); // 响应结果 return ResponseEntity.ok(new KitDynamicTableResponse<>(SystemMenuDynamicTableModel.class, pageArgs, "id", systemMenuMapper, wrapper)); } diff --git a/cutejava/cutejava-starter/src/main/java/cn/odboy/MybatisMapperBatchOpRender.java b/cutejava/cutejava-starter/src/main/java/cn/odboy/MybatisMapperBatchOpRender.java new file mode 100644 index 00000000..aee233fd --- /dev/null +++ b/cutejava/cutejava-starter/src/main/java/cn/odboy/MybatisMapperBatchOpRender.java @@ -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 void render(Class entityClass, Function 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 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); + } +}