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 @@
+
+
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
+
+
+ {{ desc }}
+
+
+ {{ desc }}
+
+
+
+
+ {{ FormatDuration(status !== statusMap.running ? durationMillis : new Date().getTime() - startTimeMillis) }}
+
+
+
+ 重试
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+ {{ taskDetail.content }}
+
+
+
+
+
+
+
+
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 super T, ? extends K> 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 super T, ? extends K> 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 super T, ? extends K> 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 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 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);
+ }
+}