add:增加AGV看板

This commit is contained in:
zhangzq
2026-01-21 17:52:35 +08:00
parent db2336a158
commit eb2eb764d7
32 changed files with 7661 additions and 209 deletions

View File

@@ -1,73 +1,130 @@
package org.nl.common.page;
import cn.hutool.core.lang.ParameterizedTypeImpl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.nl.common.util.CustomColumnUtils;
import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import org.nl.common.util.MapOf;
import java.util.concurrent.ConcurrentHashMap;
/**
* 泛型必须为数据tb对应do:由mybatis管理
* @author ZZQ
* @Date 2022/12/14 6:33 下午
*/
@Data
public class BaseQuery<T> {
/**
* 模糊查询
*/
public abstract class BaseQuery<T> {
private String blurry;
/**
* 是否启用
*/
private String is_used;
/**
* 创建时间范围查询
*/
private String start_time;
private String end_time;
/**
* 字段映射配置
*/
@JsonIgnore
protected Map<String, QParam> fieldMappings = new HashMap<String, QParam>() {{
put("blurry", QParam.builder().k(new String[]{"name"}).type(QueryTEnum.LK).build());
put("start_time", QParam.builder().k(new String[]{"create_time"}).type(QueryTEnum.LT).build());
put("end_time", QParam.builder().k(new String[]{"create_time"}).type(QueryTEnum.LE).build());
put("sort", QParam.builder().k(new String[]{"sort"}).type(QueryTEnum.BY).build());
}};
/**
* 字段映射Map:指定字段对应QueryWrapper的查询类型
* 字段与数据库字段对应,不支持驼峰
* @see org.nl.common.page.QueryTEnum
* 通过buid构建
* 实体类信息(由子类提供)
*/
public Map<String, QParam> doP = MapOf.of("blurry", QParam.builder().k(new String[]{"name"}).type(QueryTEnum.LK).build()
,"startTime", QParam.builder().k(new String[]{"create_time"}).type(QueryTEnum.LT).build()
,"endTime", QParam.builder().k(new String[]{"create_time"}).type(QueryTEnum.LE).build()
,"sort", QParam.builder().k(new String[]{"sort"}).type(QueryTEnum.BY).build()
);
@JsonIgnore
protected abstract Class<T> getEntityClass();
public QueryWrapper<T> build(){
/**
* 列名映射(子类可以重写此方法提供自定义映射)
*/
@JsonIgnore
protected Map<String, String> getColumnMappings() {
return new HashMap<>();
}
public QueryWrapper<T> build() {
this.paramMapping();
QueryWrapper<T> wrapper = new QueryWrapper<>();
JSONObject json = (JSONObject)JSONObject.toJSON(this);
Type[] types = ((ParameterizedTypeImpl) this.getClass().getGenericSuperclass()).getActualTypeArguments();
Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap((Class<?>) types[0]);
String dopStr = "doP";
json.forEach((key, vel) -> {
if (vel != null && !key.equals(dopStr)){
QParam qParam = doP.get(key);
if (qParam != null){
QueryTEnum.build(qParam.type,wrapper,qParam.k,vel);
}else {
ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(key));
if (columnCache!=null){
wrapper.eq(columnCache.getColumn(),vel);
Class<T> entityClass = getEntityClass();
Map<String, String> columnMappings = getColumnMappings();
// 获取MyBatis-Plus的列缓存
Map<String, ColumnCache> columnCacheMap = CustomColumnUtils.getColumnMap(entityClass);
// 获取所有字段
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
try {
Object value = field.get(this);
String fieldName = field.getName();
if (value == null || fieldName.equals("fieldMappings")) {
continue;
}
// 检查是否有自定义映射
QParam qParam = fieldMappings.get(fieldName);
if (qParam != null) {
QueryTEnum.build(qParam.type, wrapper, qParam.k, value);
} else {
// 查找对应的数据库列名
String columnName = findColumnName(fieldName, columnMappings, columnCacheMap);
if (columnName != null) {
wrapper.eq(columnName, value);
}
}
} catch (IllegalAccessException e) {
// 忽略无法访问的字段
}
});
}
return wrapper;
}
public void paramMapping(){};
/**
* 查找字段对应的数据库列名
*/
private String findColumnName(String fieldName,
Map<String, String> customMappings,
Map<String, ColumnCache> columnCache) {
// 1. 首先检查自定义映射
if (customMappings.containsKey(fieldName)) {
return customMappings.get(fieldName);
}
// 2. 检查列缓存(支持驼峰转下划线)
ColumnCache cache = columnCache.get(fieldName);
if (cache == null) {
// 尝试蛇形命名
StringBuilder result = new StringBuilder();
for (int i = 0; i < fieldName.length(); i++) {
char c = fieldName.charAt(i);
if (Character.isUpperCase(c)) {
result.append('_').append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
cache = columnCache.get(result.toString());
}
return cache != null ? cache.getColumn() : null;
}
/**
@Override
public void paramMapping() {
super.doP.put("pid_is_null", QParam.builder().k(new String[]{"pid"}).type(QueryTEnum.NO).build());
super.doP.put("deptIds", QParam.builder().k(new String[]{"dept_id"}).type(QueryTEnum.IN).build());
}
*/
public void paramMapping() {}
}

View File

@@ -0,0 +1,174 @@
package org.nl.common.util;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义列映射工具类,避免使用 MyBatis-Plus 的内部 API
*/
public class CustomColumnUtils {
private static final Logger logger = LoggerFactory.getLogger(CustomColumnUtils.class);
// 缓存实体类的列映射
private static final Map<Class<?>, Map<String, ColumnCache>> COLUMN_CACHE = new ConcurrentHashMap<>();
/**
* 获取实体类的列映射(替代 LambdaUtils.getColumnMap
*/
public static Map<String, ColumnCache> getColumnMap(Class<?> clazz) {
return COLUMN_CACHE.computeIfAbsent(clazz, CustomColumnUtils::buildColumnMap);
}
/**
* 构建实体类的列映射
*/
private static Map<String, ColumnCache> buildColumnMap(Class<?> clazz) {
Map<String, ColumnCache> columnMap = new HashMap<>();
try {
// 获取所有字段(包括父类)
Field[] fields = getAllFields(clazz);
for (Field field : fields) {
String fieldName = field.getName();
String columnName = getColumnName(field);
if (columnName != null) {
// 创建 ColumnCache 对象
ColumnCache columnCache = new ColumnCache(columnName, fieldName);
// 添加多种格式的键
// 1. 原始字段名
columnMap.put(fieldName, columnCache);
// 2. 驼峰格式(如果需要)
String camelCase = toCamelCase(columnName);
if (!columnMap.containsKey(camelCase)) {
columnMap.put(camelCase, columnCache);
}
// 3. 首字母小写的字段名
String firstLower = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1);
if (!firstLower.equals(fieldName) && !columnMap.containsKey(firstLower)) {
columnMap.put(firstLower, columnCache);
}
}
}
logger.debug("Built column map for class: {}, size: {}", clazz.getName(), columnMap.size());
} catch (Exception e) {
logger.error("Failed to build column map for class: {}", clazz.getName(), e);
throw new RuntimeException("Failed to build column map", e);
}
return columnMap;
}
/**
* 获取所有字段(包括父类)
*/
private static Field[] getAllFields(Class<?> clazz) {
Map<String, Field> fieldMap = new HashMap<>();
Class<?> currentClass = clazz;
while (currentClass != null && currentClass != Object.class) {
Field[] declaredFields = currentClass.getDeclaredFields();
for (Field field : declaredFields) {
// 避免覆盖子类字段
if (!fieldMap.containsKey(field.getName())) {
fieldMap.put(field.getName(), field);
}
}
currentClass = currentClass.getSuperclass();
}
return fieldMap.values().toArray(new Field[0]);
}
/**
* 获取字段对应的数据库列名
*/
private static String getColumnName(Field field) {
// 1. 检查 @TableField 注解
TableField tableField = field.getAnnotation(TableField.class);
if (tableField != null) {
// 如果字段不存在于数据库,跳过
if (!tableField.exist()) {
return null;
}
// 如果有指定的列名,使用它
if (!tableField.value().isEmpty()) {
return tableField.value();
}
}
// 2. 检查 @TableId 注解
TableId tableId = field.getAnnotation(TableId.class);
if (tableId != null && !tableId.value().isEmpty()) {
return tableId.value();
}
// 3. 默认:驼峰转下划线
return camelToUnderscore(field.getName());
}
/**
* 驼峰转下划线
*/
private static String camelToUnderscore(String camelCase) {
if (camelCase == null || camelCase.isEmpty()) {
return camelCase;
}
StringBuilder result = new StringBuilder();
result.append(Character.toLowerCase(camelCase.charAt(0)));
for (int i = 1; i < camelCase.length(); i++) {
char c = camelCase.charAt(i);
if (Character.isUpperCase(c)) {
result.append('_').append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 下划线转驼峰
*/
private static String toCamelCase(String underscore) {
if (underscore == null || underscore.isEmpty()) {
return underscore;
}
StringBuilder result = new StringBuilder();
boolean nextUpper = false;
for (int i = 0; i < underscore.length(); i++) {
char c = underscore.charAt(i);
if (c == '_') {
nextUpper = true;
} else {
if (nextUpper) {
result.append(Character.toUpperCase(c));
nextUpper = false;
} else {
result.append(c);
}
}
}
return result.toString();
}
}