feat(constool): 添加数据对比同步工具并重构动态表格构建器
- 新增 KitDiffSyncConstTool 工具类,支持集合数据的对比同步功能 - 添加数据同步回调接口和完整的增删改查批量操作支持 - 重命名 KitDynamicTableUtil 为 KitDynamicTableBuilder 并调整方法名称 - 修改 renderOrder 方法为 ofOrder,renderAll 方法为 ofAll - 更新前端 FormatDuration 函数,支持年、天、时、分、秒格式化显示 - 添加字节格式化方法 renderBytes 到 KitStringUtil 工具类 - 在首页视图中集成 Pipeline 组件用于展示任务流程 - 添加窗口打开工具函数 OpenWindowInCurrent 和 OpenWindowInNew - 创建 MybatisMapperBatchOpRender 批量操作生成器
This commit is contained in:
+168
@@ -0,0 +1,168 @@
|
||||
package cn.odboy.constool;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.odboy.base.KitBaseUserTimeTb;
|
||||
import cn.odboy.framework.exception.BadRequestException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据对比同步工具
|
||||
* <p>
|
||||
* 适用场景:对比两个相同类型的对象集合,以对照组(compareRecords)为基准:
|
||||
* <p>
|
||||
* - 本地不存在的数据 → 新增
|
||||
* <p>
|
||||
* - 本地存在的数据 → 更新
|
||||
* <p>
|
||||
* - 对照组不存在的数据 → 删除
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author odboy
|
||||
*/
|
||||
public class KitDiffSyncConstTool<T> {
|
||||
|
||||
/**
|
||||
* 数据同步回调接口
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
public interface SyncCallback<T> {
|
||||
/**
|
||||
* 全量存储对照组数据
|
||||
*
|
||||
* @param records 需要保存的数据列表
|
||||
*/
|
||||
void doSaveBatch(List<T> records);
|
||||
|
||||
/**
|
||||
* 批量更新数据
|
||||
*
|
||||
* @param localList 本地已存在的数据列表
|
||||
* @param compareList 对照组对应的数据列表
|
||||
*/
|
||||
void doUpdateBatch(List<T> localList, List<T> compareList);
|
||||
|
||||
/**
|
||||
* 批量删除数据
|
||||
*
|
||||
* @param records 需要删除的数据列表
|
||||
*/
|
||||
void doDeleteBatch(List<T> records);
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地数据源
|
||||
*/
|
||||
private final List<T> localRecords;
|
||||
|
||||
/**
|
||||
* 对照数据源(以对照数据源为准)
|
||||
*/
|
||||
private final List<T> compareRecords;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param localRecords 本地数据源
|
||||
* @param compareRecords 对照数据源(基准数据)
|
||||
*/
|
||||
public KitDiffSyncConstTool(List<T> localRecords, List<T> compareRecords) {
|
||||
this.localRecords = localRecords;
|
||||
this.compareRecords = compareRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据对比同步
|
||||
*
|
||||
* @param keyMapper 唯一键映射函数,用于比较数据唯一性(如:Entity::getId)
|
||||
* @param callback 同步回调接口,处理具体的增删改操作
|
||||
* @param <K> 唯一键类型
|
||||
*/
|
||||
public <K> void sync(Function<? super T, ? extends K> keyMapper, SyncCallback<T> callback) {
|
||||
if (keyMapper == null) {
|
||||
throw new BadRequestException("参数 keyMapper 必填");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new BadRequestException("参数 callback 必填");
|
||||
}
|
||||
|
||||
// 如果本地数据为空,直接全量保存对照数据
|
||||
if (this.localRecords.isEmpty()) {
|
||||
callback.doSaveBatch(this.compareRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建本地数据的键值映射
|
||||
Map<K, T> localMap = localRecords.stream().collect(Collectors.toMap(keyMapper, i -> i));
|
||||
|
||||
List<T> insertList = new ArrayList<>();
|
||||
List<T> updateCompareList = new ArrayList<>();
|
||||
List<T> updateLocalList = new ArrayList<>();
|
||||
List<T> deleteList = new ArrayList<>();
|
||||
|
||||
// 遍历对照数据,识别新增和更新
|
||||
for (T compareRecord : compareRecords) {
|
||||
K key = keyMapper.apply(compareRecord);
|
||||
T local = localMap.get(key);
|
||||
|
||||
if (local == null) {
|
||||
// 本地不存在,需要新增
|
||||
insertList.add(compareRecord);
|
||||
} else {
|
||||
// 本地已存在,需要更新
|
||||
updateLocalList.add(local);
|
||||
updateCompareList.add(compareRecord);
|
||||
// 标记为已处理
|
||||
localMap.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 剩余的就是本地存在但对照组中不存在的,需要删除
|
||||
for (Map.Entry<K, T> entry : localMap.entrySet()) {
|
||||
deleteList.add(entry.getValue());
|
||||
}
|
||||
|
||||
// 执行批量操作
|
||||
if (CollUtil.isNotEmpty(insertList)) {
|
||||
callback.doSaveBatch(insertList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(updateLocalList)) {
|
||||
callback.doUpdateBatch(updateLocalList, updateCompareList);
|
||||
}
|
||||
|
||||
if (CollUtil.isNotEmpty(deleteList)) {
|
||||
callback.doDeleteBatch(deleteList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用示例
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
List<KitBaseUserTimeTb> localList = new ArrayList<>();
|
||||
List<KitBaseUserTimeTb> onlineList = new ArrayList<>();
|
||||
|
||||
KitDiffSyncConstTool<KitBaseUserTimeTb> diffSyncTool = new KitDiffSyncConstTool<>(localList, onlineList);
|
||||
diffSyncTool.sync(KitBaseUserTimeTb::getCreateBy, new SyncCallback<>() {
|
||||
@Override
|
||||
public void doSaveBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 全量保存
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUpdateBatch(List<KitBaseUserTimeTb> localList, List<KitBaseUserTimeTb> compareList) {
|
||||
// 将 compareList 中的数据,填充到对应的 localList 数据中,更新 localList 数据
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDeleteBatch(List<KitBaseUserTimeTb> records) {
|
||||
// 条件删除
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 存放常用的业务工具
|
||||
*/
|
||||
package cn.odboy.constool;
|
||||
+3
-3
@@ -27,7 +27,7 @@ import lombok.experimental.UtilityClass;
|
||||
* @author odboy
|
||||
*/
|
||||
@UtilityClass
|
||||
public class KitDynamicTableUtil {
|
||||
public class KitDynamicTableBuilder {
|
||||
|
||||
/**
|
||||
* 渲染排序规则
|
||||
@@ -37,7 +37,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> QueryWrapper<M> renderOrder(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> QueryWrapper<M> ofOrder(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = new QueryWrapper<>();
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
@@ -54,7 +54,7 @@ public class KitDynamicTableUtil {
|
||||
* @param <T> 渲染对象
|
||||
* @return /
|
||||
*/
|
||||
public static <M, T> LambdaQueryWrapper<M> renderAll(KitPageArgs<T> pageArgs) {
|
||||
public static <M, T> LambdaQueryWrapper<M> ofAll(KitPageArgs<T> pageArgs) {
|
||||
QueryWrapper<M> wrapper = KitMpQUtil.build(pageArgs.getArgs());
|
||||
KitPageArgs.OrderBy orderBy = pageArgs.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
+98
-62
@@ -25,74 +25,110 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@UtilityClass
|
||||
public final class KitStringUtil {
|
||||
|
||||
private static final char SEPARATOR = '_';
|
||||
private static final char SEPARATOR = '_';
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = s.toLowerCase();
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c == SEPARATOR) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
upperCase = false;
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toCapitalizeCamelCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld"
|
||||
* toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
s = toCamelCase(s);
|
||||
return s.substring(0, 1).toUpperCase() + s.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字节数
|
||||
*
|
||||
* @param storage 字节数
|
||||
* @return /
|
||||
*/
|
||||
private String renderBytes(Long storage) {
|
||||
if (storage == null || storage <= 0) {
|
||||
return "0 Byte";
|
||||
}
|
||||
|
||||
/**
|
||||
* 驼峰命名法工具
|
||||
*
|
||||
* @return toCamelCase(" hello_world ") == "helloWorld" toCapitalizeCamelCase("hello_world") == "HelloWorld" toUnderScoreCase("helloWorld") = "hello_world"
|
||||
*/
|
||||
public static String toUnderScoreCase(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
boolean nextUpperCase = true;
|
||||
if (i < (s.length() - 1)) {
|
||||
nextUpperCase = Character.isUpperCase(s.charAt(i + 1));
|
||||
}
|
||||
if ((i > 0) && Character.isUpperCase(c)) {
|
||||
if (!upperCase || !nextUpperCase) {
|
||||
sb.append(SEPARATOR);
|
||||
double value = storage.doubleValue();
|
||||
final long KB = 1024L;
|
||||
final long MB = KB * 1024L;
|
||||
final long GB = MB * 1024L;
|
||||
final long TB = GB * 1024L;
|
||||
final long PB = TB * 1024L;
|
||||
|
||||
if (value >= PB) {
|
||||
return String.format("%.2f PB", value / PB);
|
||||
} else if (value >= TB) {
|
||||
return String.format("%.2f TB", value / TB);
|
||||
} else if (value >= GB) {
|
||||
return String.format("%.2f GB", value / GB);
|
||||
} else if (value >= MB) {
|
||||
return String.format("%.2f MB", value / MB);
|
||||
} else if (value >= KB) {
|
||||
return String.format("%.2f KB", value / KB);
|
||||
} else {
|
||||
return String.format("%d Byte", storage);
|
||||
}
|
||||
upperCase = true;
|
||||
} else {
|
||||
upperCase = false;
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user