应用场景
当外部多个模板数据需要统一导入到系统中时,起初代码各种判断针对不同模板编写不同的转换规则。
随着外部模板不端增加代码便成了屎山难以维护。
这时需要一个通用的解决方案不再每次添加模板增加代码。
调研后发现表达式引擎fel满足需求。
参考文章:
1.Java动态脚本&规则引擎、计算/表达式引擎
https://juejin.cn/post/6981474006293544991
2.Fel表达式计算引擎学习
https://www.cnblogs.com/boothsun/p/7995387.html
代码实现:
study-thread/src/main/java/cn/zy/study/thread/fel
jel自定义函数封装
package com.hzqc.modules.imports;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.function.CommonFunction;
import com.greenpineyu.fel.function.Function;
import com.hzqc.exception.BadException400;
import com.hzqc.utils.DateUtil;
import com.hzqc.utils.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class FelUtils {
public static FelEngine getFelEngine() {
FelEngine fel = FelEngine.instance;
fel.addFun(objectResultIsNotNull());
fel.addFun(booleanResultIsNotNull());
fel.addFun(checkLength());
fel.addFun(setDefault());
fel.addFun(contains());
fel.addFun(echoPhone());
fel.addFun(echoVin());
fel.addFun(dateFormatString());
fel.addFun(setScale());
fel.addFun(safeIntercept());
return fel;
}
public static Function safeIntercept() {
return new CommonFunction() {
@Override
public String getName() {
return "safeIntercept";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
String bxgs = String.valueOf(arguments[0]);
if (StringUtils.isNotBlanks(bxgs) && bxgs.contains("送修")) {
bxgs = bxgs.split("送修").length == 0 ? "送修" : bxgs.split("送修")[0];
return bxgs + "送修";
}
return null;
} else {
return null;
}
}
};
}
public static Function setScale() {
return new CommonFunction() {
@Override
public String getName() {
return "setScale";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object str = arguments[0];
Integer length = (Integer) arguments[1];
String typeName = str.getClass().getTypeName();
BigDecimal covertBigdecimal = new BigDecimal(String.valueOf(str)).setScale(length, RoundingMode.HALF_UP);
if ("java.math.BigDecimal".equals(typeName)) {
return covertBigdecimal;
} else if ("java.lang.Double".equals(typeName)) {
return covertBigdecimal.doubleValue();
} else if ("java.lang.Folat".equals(typeName)) {
return covertBigdecimal.floatValue();
} else {
return str;
}
} else {
return null;
}
}
};
}
public static Function dateFormatString() {
return new CommonFunction() {
@Override
public String getName() {
return "dateFormatString";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object str = arguments[0];
Boolean isMust = (Boolean) arguments[1];
String erroFiled = (String) arguments[3];
String format = (String) arguments[2];
if (isMust && StringUtils.isBlanks(str)) {
throw new BadException400(erroFiled + ":不允许为空!");
}
return str == null ? null : DateUtil.format((Date) str, format);
} else {
return null;
}
}
};
}
public static Function objectResultIsNotNull() {
return new CommonFunction() {
@Override
public String getName() {
return "objectResultIsNotNull";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object str = arguments[0];
Boolean isMust = (Boolean) arguments[1];
String erroFiled = (String) arguments[2];
if (isMust && StringUtils.isBlanks(str)) {
throw new BadException400(erroFiled + ":不允许为空!");
}
return str;
} else {
return null;
}
}
};
}
public static Function booleanResultIsNotNull() {
return new CommonFunction() {
@Override
public String getName() {
return "booleanResultIsNotNull";
}
@Override
public Boolean call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object str = arguments[0];
Boolean isMust = (Boolean) arguments[1];
String erroFiled = (String) arguments[2];
if (isMust && StringUtils.isBlanks(str)) {
throw new BadException400(erroFiled + ":不允许为空!");
}
return true;
} else {
return false;
}
}
};
}
public static Function checkLength() {
return new CommonFunction() {
@Override
public String getName() {
return "checkLength";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
String str = String.valueOf(arguments[0]);
Integer length = (Integer) arguments[1];
String erroFiled = (String) arguments[2];
if (str != null && str.length() > length) {
throw new BadException400(String.format("[%s]长度过长、当前长度[%s]、限制长度[%s]", erroFiled, str.length(), length));
}
return arguments[0];
} else {
return null;
}
}
};
}
public static Function echoPhone() {
return new CommonFunction() {
@Override
public String getName() {
return "echoPhone";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object phone = arguments[0];
return StringUtils.echoPhone(String.valueOf(phone));
} else {
return null;
}
}
};
}
public static Function echoVin() {
return new CommonFunction() {
@Override
public String getName() {
return "echoVin";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object vin = arguments[0];
return StringUtils.echoVin(String.valueOf(vin));
} else {
return null;
}
}
};
}
public static Function setDefault() {
return new CommonFunction() {
@Override
public String getName() {
return "setDefault";
}
@Override
public Object call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
Object filed = arguments[0];
Object defaultValue = arguments[1];
return (filed == null || filed == "") ? defaultValue : filed;
} else {
return null;
}
}
};
}
public static Function contains() {
return new CommonFunction() {
@Override
public String getName() {
return "contains";
}
@Override
public Boolean call(Object[] arguments) {
if (arguments != null && arguments.length > 0) {
String listString = (String) arguments[0];
String[] stringArray = listString.split(",");
List<String> list = Arrays.asList(stringArray);
Object currentValue = String.valueOf(arguments[1]);
return list.contains(currentValue);
} else {
return false;
}
}
};
}
}
数据库表设计
CREATE TABLE `upload_templet` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`table_name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '模板对应表名',
`table_remark` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '表备注',
`diff_field` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模板区分字段名称',
`diff_field_value` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '模板区分字段值',
`templet_name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '模板名称',
`enabled` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否启用:0禁用 1启用',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='导入模板';
CREATE TABLE `upload_filed_mapping` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`temp_id` bigint(20) NOT NULL COMMENT '(upload_templet) id',
`is_database` bit(1) NOT NULL DEFAULT b'1' COMMENT '是否数据库字段',
`field_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字段名',
`field_remark` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '字段名描述',
`mapping_column` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '映射excel列名',
`filed_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '数据库字段类型',
`sorts` int(3) DEFAULT NULL COMMENT '排序',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`),
UNIQUE KEY `temp_id_is_database_field_name` (`temp_id`,`is_database`,`field_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='模板表字段映射';
CREATE TABLE `upload_terms_mapping` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`filed_id` bigint(20) NOT NULL COMMENT 'updload_filed_mapping id',
`terms_exp` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'true' COMMENT '条件表达式',
`result_exp` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '结果表达式',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '修改人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='条件映射表';
导入工具
package com.hzqc.modules.imports;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.greenpineyu.fel.FelEngine;
import com.greenpineyu.fel.context.FelContext;
import com.hzqc.exception.BadException400;
import com.hzqc.exception.BadExceptionNoMsg;
import com.hzqc.model.ExcelMap;
import com.hzqc.modules.imports.domain.UploadFiledMapping;
import com.hzqc.modules.imports.domain.UploadTemplet;
import com.hzqc.modules.imports.domain.UploadTermsMapping;
import com.hzqc.modules.imports.repository.UploadFiledMappingRepository;
import com.hzqc.modules.imports.repository.UploadTempletRepository;
import com.hzqc.modules.imports.repository.UploadTermsMappingRepository;
import com.hzqc.modules.mnt.model.ExcelSocketMsg;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class ImportService {
private final UploadTempletRepository uploadTempletRepository;
private final UploadFiledMappingRepository uploadFiledMappingRepository;
private final UploadTermsMappingRepository uploadTermsMappingRepository;
public <T> List<T> covertList(List<ExcelMap> list, Long templetId, Class<T> beanClass, ExcelSocketMsg msg) throws Exception {
List<T> listResult = new ArrayList<>();
int row = 1;
try {
for (ExcelMap map : list) {
row++;
T covert = covert(map, templetId, beanClass);
listResult.add(covert);
msg.sendMsg("已组装" + listResult.size() + "条数据");
}
return listResult;
} catch (Exception e) {
msg.sendError(row, e);
throw new BadException400("excel数据转换异常!");
}
}
public <T> T covert(ExcelMap map, Long templetId, Class<T> beanClass) throws Exception {
UploadTemplet uploadTemplet = uploadTempletRepository.findById(templetId)
.orElseThrow(() -> new BadExceptionNoMsg("导入excel模板不存在"));
log.info("导入数据模板为:" + JSON.toJSONString(uploadTemplet));
List<UploadFiledMapping> listFiledMapings = uploadFiledMappingRepository.findByTempId(uploadTemplet.getId());
List<UploadFiledMapping> sortListFiledMappings = listFiledMapings.stream().sorted(Comparator.comparingInt(UploadFiledMapping::getSorts)).collect(Collectors.toList());
Map<String, Object> map1 = new HashMap<>();
FelEngine felEngine = FelUtils.getFelEngine();
FelContext felContext = felEngine.getContext();
sortListFiledMappings.forEach(filedMapping -> {
List<UploadTermsMapping> listTermsMappings = uploadTermsMappingRepository.findByFiledId(filedMapping.getId());
Object value = getObject(map, filedMapping);
if (CollUtil.isNotEmpty(listTermsMappings)) {
value = mappingProcessing(listTermsMappings, felEngine, felContext);
}
log.info("当前转换字段:" + filedMapping.getFieldName() + ".转换后的值:" + value);
map1.put(filedMapping.getFieldName(), value);
felContext.set(filedMapping.getFieldName(), value);
});
return beanClass.cast(mapToBean(map1, beanClass));
}
private Object mappingProcessing(List<UploadTermsMapping> listTermsMappings, FelEngine felEngine, FelContext felContext) {
Object value = null;
try {
if (listTermsMappings.size() == 1) {
UploadTermsMapping uploadTermsMapping = listTermsMappings.get(0);
String termsExp = uploadTermsMapping.getTermsExp();
String resultExp = uploadTermsMapping.getResultExp();
Boolean eval = (Boolean) felEngine.eval(termsExp, felContext);
if (eval) {
value = felEngine.eval(resultExp, felContext);
}
} else {
Boolean flag = false;
for (UploadTermsMapping listTermsMapping : listTermsMappings) {
String termsExp = listTermsMapping.getTermsExp();
String resultExp = listTermsMapping.getResultExp();
Boolean eval = (Boolean) felEngine.eval(termsExp, felContext);
if (eval) {
value = felEngine.eval(resultExp, felContext);
flag = true;
break;
}
}
if (!flag) {
log.error("未匹配到正确的条件!");
}
}
} catch (Exception e) {
throw new BadException400(String.format("表达式识别异常:异常表达式:[%s].异常信息:[%s].", JSON.toJSONString(listTermsMappings), e.getMessage()));
}
return value;
}
public <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) throws InstantiationException, IllegalAccessException {
T object = beanClass.newInstance();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
int mod = field.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
ReflectionUtils.makeAccessible(field);
if (map.containsKey(field.getName())) {
if (map.get(field.getName()) != null) {
String mapTypeName = map.get(field.getName()).getClass().getTypeName();
String beanTypeName = field.getType().getTypeName();
if (!mapTypeName.equals(beanTypeName)) {
if ("java.lang.Double".equals(mapTypeName) && "java.math.BigDecimal".equals(beanTypeName)) {
ReflectionUtils.setField(field, object, BigDecimal.valueOf((Double) map.get(field.getName())));
continue;
}
if ("java.lang.Integer".equals(mapTypeName) && "java.math.BigDecimal".equals(beanTypeName)) {
ReflectionUtils.setField(field, object, new BigDecimal(Integer.parseInt(map.get(field.getName()).toString())));
continue;
}
throw new BadException400(String.format("反射转换实体时类型对应异常!异常字段名:[%s].异常字段mapType:[%s].异常字段beanType:[%s].", field.getName(), mapTypeName, beanTypeName));
}
}
ReflectionUtils.setField(field, object, map.get(field.getName()));
}
}
return object;
}
public static <T> Map<String, Object> beanToMap(T object) throws IllegalAccessException {
Map<String, Object> map = new HashMap<>();
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
ReflectionUtils.makeAccessible(field);
map.put(field.getName(), field.get(object));
}
return map;
}
public static Object getObject(ExcelMap map, UploadFiledMapping filedMapping) {
String mappingColumn = filedMapping.getMappingColumn();
if (!"无".equals(mappingColumn) && map.get(mappingColumn) == null) {
throw new BadException400("表头无法对应请检查模板是否正确!异常表头:" + mappingColumn);
}
String filedType = filedMapping.getFiledType();
Object value;
switch (filedType) {
case "Long":
value = map.getLong(mappingColumn);
break;
case "Integer":
value = map.getInteger(mappingColumn);
break;
case "BigDecimal":
value = map.getBigDecimal(mappingColumn);
break;
case "Date":
value = map.getDate(mappingColumn);
break;
case "String":
value = map.getString(mappingColumn);
break;
case "Boolean":
value = map.getBoolean(mappingColumn);
break;
default:
throw new BadException400("类型配置异常!-->" + filedType + "-->" + mappingColumn);
}
return value;
}
}