From d8dd80a274a751ec24a7f9d0018e65c6caf2b1ce Mon Sep 17 00:00:00 2001 From: lihongjie0209 Date: Mon, 3 Jun 2024 22:04:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=AC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coal/common/GroovyScriptUtils.java | 82 ++++++++++++- .../lihongjie/coal/common/ReflectUtils.java | 37 +++--- .../dto/WeightColumnConfigDto.java | 7 ++ .../service/WeightColumnConfigService.java | 42 ++++--- .../dto/WeightDeviceDataReportRequest.java | 4 + .../service/WeightDeviceDataService.java | 110 +++++++++++++----- .../coal/common/GroovyScriptUtilsTest.java | 56 +++++++++ 7 files changed, 274 insertions(+), 64 deletions(-) diff --git a/src/main/java/cn/lihongjie/coal/common/GroovyScriptUtils.java b/src/main/java/cn/lihongjie/coal/common/GroovyScriptUtils.java index 83159759..a84ca1eb 100644 --- a/src/main/java/cn/lihongjie/coal/common/GroovyScriptUtils.java +++ b/src/main/java/cn/lihongjie/coal/common/GroovyScriptUtils.java @@ -2,17 +2,26 @@ package cn.lihongjie.coal.common; import cn.lihongjie.coal.exception.BizException; -import groovy.lang.Binding; -import groovy.lang.GroovyShell; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import groovy.lang.Binding; +import groovy.lang.GroovyClassLoader; +import groovy.lang.Script; + +import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.codehaus.groovy.ast.ASTNode; -import org.codehaus.groovy.ast.GroovyCodeVisitorAdapter; +import org.codehaus.groovy.ast.CodeVisitorSupport; import org.codehaus.groovy.ast.builder.AstStringCompiler; import org.codehaus.groovy.ast.expr.VariableExpression; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.springframework.util.StopWatch; import java.util.ArrayList; import java.util.List; @@ -26,6 +35,12 @@ import java.util.stream.Collectors; @Slf4j public class GroovyScriptUtils { + private static final Cache> scriptCache = + CacheBuilder.newBuilder().maximumSize(10000).build(); + + private static final Cache, Script> scriptInstanceCache = + CacheBuilder.newBuilder().maximumSize(10000).build(); + public String replaceVariable( String patternstr, String script, Function mapper) { @@ -60,6 +75,12 @@ public class GroovyScriptUtils { throw new BizException("无效的计算公式: " + e.getMessage()); } } + private static final GroovyClassLoader groovyClassLoader; + + static { + CompilerConfiguration config = new CompilerConfiguration(); + groovyClassLoader = new GroovyClassLoader(GroovyScriptUtils.class.getClassLoader(), config); + } public static List variables(String formula) { if (StringUtils.isEmpty(formula)) { @@ -73,7 +94,7 @@ public class GroovyScriptUtils { x -> { ArrayList ans = new ArrayList<>(); x.visit( - new GroovyCodeVisitorAdapter() { + new CodeVisitorSupport() { @Override public void visitVariableExpression( VariableExpression expression) { @@ -83,13 +104,62 @@ public class GroovyScriptUtils { return ans.stream(); }) + .distinct() .collect(Collectors.toList()); } + @SneakyThrows public static Object exec(String formula0, Map map) { + return exec(formula0, map, true, false); + } - GroovyShell shell = new GroovyShell(new Binding(map)); + @SneakyThrows + public static Object exec(String formula0, Map map, boolean reuseScriptInstance, boolean logTime) { - return shell.evaluate(formula0); + if (StringUtils.isEmpty(formula0)) { + return null; + } + + if (MapUtils.isEmpty(map)) { + + map = Map.of(); + } + + String key = DigestUtils.md5Hex(formula0); + StopWatch stopWatch = null; + if (logTime) { + + stopWatch = new StopWatch("exec " + key); + } + + if (stopWatch != null) stopWatch.start("parseClass"); + + Class parsedClass = scriptCache.get(key, () -> groovyClassLoader.parseClass(formula0)); + + if (stopWatch != null) stopWatch.stop(); + + if (stopWatch != null) stopWatch.start("newInstance"); + + Script script = reuseScriptInstance ? scriptInstanceCache.get(parsedClass, () -> (Script) parsedClass.newInstance()): (Script) parsedClass.newInstance(); + + + if (stopWatch != null) stopWatch.stop(); + + if (stopWatch != null) stopWatch.start("binding"); + script.setBinding(new Binding(map)); + + if (stopWatch != null) stopWatch.stop(); + + if (stopWatch != null) stopWatch.start("run"); + + Object run = script.run(); + + if (stopWatch != null) stopWatch.stop(); + + if (stopWatch != null) { + + log.info(stopWatch.prettyPrint()); + } + return run; } } diff --git a/src/main/java/cn/lihongjie/coal/common/ReflectUtils.java b/src/main/java/cn/lihongjie/coal/common/ReflectUtils.java index 2ee94289..c597206f 100644 --- a/src/main/java/cn/lihongjie/coal/common/ReflectUtils.java +++ b/src/main/java/cn/lihongjie/coal/common/ReflectUtils.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull; import org.springframework.util.ReflectionUtils; import java.lang.reflect.Field; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -106,9 +107,8 @@ public class ReflectUtils { return fieldCache.get(cls, () -> FieldUtils.getAllFieldsList(cls)); } - @SneakyThrows - public static List getAllFieldsList(Class cls){ + public static List getAllFieldsList(Class cls) { return doGetAllFields(cls); } @@ -135,14 +135,11 @@ public class ReflectUtils { return null; } - return getField(o.getClass(), field) - .map(x -> getReadField(x, o, forceAccess)) - .orElse(null); - + return getField(o.getClass(), field).map(x -> readField(x, o, forceAccess)).orElse(null); } @SneakyThrows - public static Object getReadField(Field x, Object o, boolean forceAccess){ + public static Object readField(Field x, Object o, boolean forceAccess) { return FieldUtils.readField(x, o, forceAccess); } @@ -157,12 +154,13 @@ public class ReflectUtils { return; } - getField(o.getClass(), field) - .ifPresent(x -> writeField(x, o, val, forceAccess)); + getField(o.getClass(), field).ifPresent(x -> writeField(x, o, val, forceAccess)); } + @SneakyThrows - public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess){ - FieldUtils.writeField(field, target, value, forceAccess); + public static void writeField( + final Field field, final Object target, final Object value, final boolean forceAccess) { + FieldUtils.writeField(field, target, value, forceAccess); } public static Object getFieldValue(Object object, String fieldName) { @@ -185,13 +183,24 @@ public class ReflectUtils { public static List idList(Object dtos) { - - if (dtos instanceof Iterable) { return ((List) dtos).stream().map(ReflectUtils::getId).toList(); - }else { + } else { return List.of(ReflectUtils.getId(dtos)); } } + + public static Map toMap(Object entity) { + + if (entity == null) { + return Map.of(); + } + + Map map = new HashMap<>(); + for (Field e : getAllFieldsList(entity.getClass())) { + map.put(e.getName(), readField(e, entity, true)); + } + return map; + } } diff --git a/src/main/java/cn/lihongjie/coal/weightColumnConfig/dto/WeightColumnConfigDto.java b/src/main/java/cn/lihongjie/coal/weightColumnConfig/dto/WeightColumnConfigDto.java index 6c21521b..1e437f8e 100644 --- a/src/main/java/cn/lihongjie/coal/weightColumnConfig/dto/WeightColumnConfigDto.java +++ b/src/main/java/cn/lihongjie/coal/weightColumnConfig/dto/WeightColumnConfigDto.java @@ -4,8 +4,15 @@ import cn.lihongjie.coal.base.dto.OrgCommonDto; import lombok.Data; +import org.hibernate.annotations.Comment; + @Data public class WeightColumnConfigDto extends OrgCommonDto { private String displayName; private Integer width; + + + + @Comment("groovy脚本") + private String script; } diff --git a/src/main/java/cn/lihongjie/coal/weightColumnConfig/service/WeightColumnConfigService.java b/src/main/java/cn/lihongjie/coal/weightColumnConfig/service/WeightColumnConfigService.java index 752191c1..6e2c74f3 100644 --- a/src/main/java/cn/lihongjie/coal/weightColumnConfig/service/WeightColumnConfigService.java +++ b/src/main/java/cn/lihongjie/coal/weightColumnConfig/service/WeightColumnConfigService.java @@ -18,9 +18,13 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; @@ -68,6 +72,8 @@ public class WeightColumnConfigService return getById(entity.getId()); } + @PersistenceContext EntityManager em; + private void validateScript(UpdateWeightColumnConfigDto request) { if (StringUtils.isBlank(request.getScript())) { @@ -77,10 +83,31 @@ public class WeightColumnConfigService String script = request.getScript(); GroovyScriptUtils.validate(script); + + List variables = GroovyScriptUtils.variables(request.getScript()); + + if (CollectionUtils.isNotEmpty(variables)) { + + + for (String variable : variables) { + + + Long cnt = (Long) em.createQuery("select count(*) from WeightColumnConfigEntity where code = :variable and status = 1 and organizationId = :organizationId") + .setParameter("variable", variable) + .setParameter("organizationId", Ctx.currentUser().getOrganizationId()) + .getSingleResult(); + + if ( cnt == null || cnt == 0) { + throw new RuntimeException("变量不存在或者被禁用: " + variable); + } + + + } + + } } - public void runScriptOnOldData(IdRequest request){ - + public void runScriptOnOldData(IdRequest request) { WeightColumnConfigEntity entity = this.repository.get(request.getIds().get(0)); @@ -89,19 +116,8 @@ public class WeightColumnConfigService } String script = entity.getScript(); - - - - - - - - - } - - public void delete(IdRequest request) { this.repository.deleteAllById(request.getIds()); } diff --git a/src/main/java/cn/lihongjie/coal/weightDeviceData/dto/WeightDeviceDataReportRequest.java b/src/main/java/cn/lihongjie/coal/weightDeviceData/dto/WeightDeviceDataReportRequest.java index d3f86c17..fab8e93e 100644 --- a/src/main/java/cn/lihongjie/coal/weightDeviceData/dto/WeightDeviceDataReportRequest.java +++ b/src/main/java/cn/lihongjie/coal/weightDeviceData/dto/WeightDeviceDataReportRequest.java @@ -51,6 +51,10 @@ hour private List mzUserList; private String pzUser; private List pzUserList; + private List weighTypeList; + + + @Data public static class FieldInfo { diff --git a/src/main/java/cn/lihongjie/coal/weightDeviceData/service/WeightDeviceDataService.java b/src/main/java/cn/lihongjie/coal/weightDeviceData/service/WeightDeviceDataService.java index 18f5671e..2e5f37a2 100644 --- a/src/main/java/cn/lihongjie/coal/weightDeviceData/service/WeightDeviceDataService.java +++ b/src/main/java/cn/lihongjie/coal/weightDeviceData/service/WeightDeviceDataService.java @@ -4,12 +4,15 @@ import cn.lihongjie.coal.base.dto.CommonQuery; import cn.lihongjie.coal.base.dto.IdRequest; import cn.lihongjie.coal.base.service.BaseService; import cn.lihongjie.coal.common.Ctx; +import cn.lihongjie.coal.common.GroovyScriptUtils; import cn.lihongjie.coal.common.JpaUtils; +import cn.lihongjie.coal.common.ReflectUtils; import cn.lihongjie.coal.dataCollector.service.DataCollectorService; import cn.lihongjie.coal.dataCollectorLog.service.DataCollectorLogService; import cn.lihongjie.coal.dbFunctions.DbFunctionService; import cn.lihongjie.coal.exception.BizException; import cn.lihongjie.coal.rabbitmq.RabbitMQService; +import cn.lihongjie.coal.weightColumnConfig.entity.WeightColumnConfigEntity; import cn.lihongjie.coal.weightDevice.entity.WeightDeviceEntity; import cn.lihongjie.coal.weightDevice.mapper.WeightDeviceMapper; import cn.lihongjie.coal.weightDevice.service.WeightDeviceService; @@ -49,6 +52,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -343,6 +347,46 @@ where 更新时间>='%s' and 更新时间<='%s' this.repository.unArchive(dto); } + @Override + public WeightDeviceDataEntity save(WeightDeviceDataEntity entity) { + + List configs = + em.createQuery( + "select w from WeightColumnConfigEntity w where w.organizationId = :organizationId and w.script is not null and length(w.script) > 0 and w.status = 1 order by w.sortKey asc ", + WeightColumnConfigEntity.class) + .setParameter("organizationId", entity.getOrganizationId()) + .getResultList(); + + if (!CollectionUtils.isEmpty(configs)) { + + for (WeightColumnConfigEntity config : configs) { + + if (StringUtils.isNotBlank(config.getScript())) { + + Object result = null; + + try { + + result = + GroovyScriptUtils.exec( + config.getScript(), ReflectUtils.toMap(entity)); + } catch (Exception e) { + log.error("exec script error {}", config.getScript(), e); + } + + try { + + ReflectUtils.writeField(entity, config.getCode(), result); + } catch (Exception e) { + log.error("write field error {}", config.getCode(), e); + } + } + } + } + + return super.save(entity); + } + public Page report(WeightDeviceDataReportRequest request) { if (StringUtils.isEmpty(request.getTimeDimension())) { @@ -429,11 +473,6 @@ where 更新时间>='%s' and 更新时间<='%s' where += " and d.specification = :specification "; } - - - - - if (CollectionUtils.isNotEmpty(request.getPlateNoList())) { where += " and d.plate_no in :plateNoList "; } @@ -454,7 +493,6 @@ where 更新时间>='%s' and 更新时间<='%s' where += " and d.specification in :specificationList "; } - if (CollectionUtils.isNotEmpty(request.getMzUserList())) { where += " and d.mz_user in :mzUserList "; } @@ -463,8 +501,9 @@ where 更新时间>='%s' and 更新时间<='%s' where += " and d.pz_user in :pzUserList "; } - - + if (CollectionUtils.isNotEmpty(request.getWeighTypeList())) { + where += " and d.weigh_type in :weighTypeList "; + } where += " and ( d.invalid is null or not d.invalid ) "; where += " and ( d.finished is null or d.finished ) "; @@ -540,7 +579,6 @@ where 更新时间>='%s' and 更新时间<='%s' countQuery.setParameter("specification", "%" + request.getSpecification() + "%"); } - if (CollectionUtils.isNotEmpty(request.getPlateNoList())) { selectQuery.setParameter("plateNoList", request.getPlateNoList()); countQuery.setParameter("plateNoList", request.getPlateNoList()); @@ -551,10 +589,11 @@ where 更新时间>='%s' and 更新时间<='%s' countQuery.setParameter("sendOrganizationList", request.getSendOrganizationList()); } - if (CollectionUtils.isNotEmpty(request.getReceiveOrganizationList())) { - selectQuery.setParameter("receiveOrganizationList", request.getReceiveOrganizationList()); - countQuery.setParameter("receiveOrganizationList", request.getReceiveOrganizationList()); + selectQuery.setParameter( + "receiveOrganizationList", request.getReceiveOrganizationList()); + countQuery.setParameter( + "receiveOrganizationList", request.getReceiveOrganizationList()); } if (CollectionUtils.isNotEmpty(request.getGoodsList())) { @@ -562,29 +601,26 @@ where 更新时间>='%s' and 更新时间<='%s' countQuery.setParameter("goodsList", request.getGoodsList()); } - if (CollectionUtils.isNotEmpty(request.getSpecificationList())) { selectQuery.setParameter("specificationList", request.getSpecificationList()); countQuery.setParameter("specificationList", request.getSpecificationList()); } - - - if (CollectionUtils.isNotEmpty(request.getMzUserList())){ + if (CollectionUtils.isNotEmpty(request.getMzUserList())) { selectQuery.setParameter("mzUserList", request.getMzUserList()); countQuery.setParameter("mzUserList", request.getMzUserList()); } - if (CollectionUtils.isNotEmpty(request.getPzUserList())){ + if (CollectionUtils.isNotEmpty(request.getPzUserList())) { selectQuery.setParameter("pzUserList", request.getPzUserList()); countQuery.setParameter("pzUserList", request.getPzUserList()); } + if (CollectionUtils.isNotEmpty(request.getWeighTypeList())) { - - - - + selectQuery.setParameter("weighTypeList", request.getWeighTypeList()); + countQuery.setParameter("weighTypeList", request.getWeighTypeList()); + } var resultList = JpaUtils.convertTuplesToMap(selectQuery.getResultList()); @@ -604,7 +640,12 @@ where 更新时间>='%s' and 更新时间<='%s' .LOWER_CAMEL, e.getKey() .toString()), - e -> ObjectUtils.defaultIfNull(e.getValue(), "")))) + e -> + ObjectUtils + .defaultIfNull( + e + .getValue(), + "")))) .toList(); return new PageImpl<>( @@ -618,30 +659,37 @@ where 更新时间>='%s' and 更新时间<='%s' int cnt = em.createQuery( "update WeightDeviceDataEntity d set d.invalid = true where d.sequenceNumber not in :ids and d.dataUpdateTime >= :start and d.dataUpdateTime <= :end and d.device.id = :id and d.archiveStatus = '0'") - .setParameter("ids", seqs) + .setParameter("ids", Arrays.stream(seqs).mapToObj(x -> x).toList()) .setParameter("start", start) .setParameter("end", end) .setParameter("id", id) .executeUpdate(); - log.info("invalidate {} records, start {} end {} deviceId {} seqsLen {} ", cnt, start, end, id, seqs.length); - - + log.info( + "invalidate {} records, start {} end {} deviceId {} seqsLen {} ", + cnt, + start, + end, + id, + seqs.length); int cnt2 = em.createQuery( "update WeightDeviceDataEntity d set d.invalid = false where d.sequenceNumber in :ids and d.dataUpdateTime >= :start and d.dataUpdateTime <= :end and d.device.id = :id and d.archiveStatus = '0'") - .setParameter("ids", seqs) + .setParameter("ids", Arrays.stream(seqs).mapToObj(x -> x).toList()) .setParameter("start", start) .setParameter("end", end) .setParameter("id", id) .executeUpdate(); - log.info("validate {} records, start {} end {} deviceId {} seqsLen {} ", cnt2, start, end, id, seqs.length); + log.info( + "validate {} records, start {} end {} deviceId {} seqsLen {} ", + cnt2, + start, + end, + id, + seqs.length); return cnt; - - - } } diff --git a/src/test/java/cn/lihongjie/coal/common/GroovyScriptUtilsTest.java b/src/test/java/cn/lihongjie/coal/common/GroovyScriptUtilsTest.java index 4a0f24df..aa2c4b09 100644 --- a/src/test/java/cn/lihongjie/coal/common/GroovyScriptUtilsTest.java +++ b/src/test/java/cn/lihongjie/coal/common/GroovyScriptUtilsTest.java @@ -17,4 +17,60 @@ class GroovyScriptUtilsTest { assertEquals( "1 + 1", GroovyScriptUtils.replaceVariable("\\【(.+?)\\】", "1 + 1", (s) -> "1")); } + + @Test + void testExec() { + + assertEquals(1, GroovyScriptUtils.exec("1", new HashMap<>())); + assertEquals(2, GroovyScriptUtils.exec("1 + 1", new HashMap<>())); + assertEquals(2, GroovyScriptUtils.exec("a + b", Map.of("a", 1, "b", 1))); + assertEquals( + 2, + GroovyScriptUtils.exec( + "(a + (b?.isDouble() ? b.toDouble() : 0)) as Integer", + Map.of("a", 1, "b", "1"))); + assertEquals( + 1, + GroovyScriptUtils.exec( + "(a + (b?.isDouble() ? b.toDouble() : 0)) as Integer", + Map.of("a", 1, "b", ""))); + } + + @Test + void testExec1000() { + + for (int i = 0; i < 10000; i++) { + + assertEquals(2, GroovyScriptUtils.exec("a + b", Map.of("a", 1, "b", 1), false,false)); + } + } + + @Test + void testExec1000_reuse() { + + for (int i = 0; i < 10000; i++) { + + assertEquals(2, GroovyScriptUtils.exec("a + b", Map.of("a", 1, "b", 1), true,false)); + } + } + + @Test + void testVariables() { + + assertEquals( + List.of("a", "b"), + GroovyScriptUtils.variables("a + b")); + assertEquals( + List.of("a", "b"), + GroovyScriptUtils.variables("a + b + a")); + assertEquals( + List.of("a", "b"), + GroovyScriptUtils.variables("a + b + a + b")); + assertEquals( + List.of("a", "b", "c"), + GroovyScriptUtils.variables("a + b + a + b + c")); + + + + } }