This commit is contained in:
2024-05-01 20:21:08 +08:00
parent 73f349f368
commit efe3338743
9 changed files with 733 additions and 16 deletions

View File

@@ -54,6 +54,11 @@
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>

View File

@@ -149,6 +149,13 @@ public class CommonQuery {
parseKey(root, x.key), c.convert(x.value, String.class));
});
map.put(
Tuple.of("eq", Boolean.class),
(Root root, CriteriaBuilder criteriaBuilder, QueryItem x, ConversionService c) -> {
return criteriaBuilder.equal(
parseKey(root, x.key), c.convert(x.value, Boolean.class));
});
map.put(
Tuple.of("eq", Integer.class),
(Root root, CriteriaBuilder criteriaBuilder, QueryItem x, ConversionService c) -> {
@@ -207,6 +214,12 @@ public class CommonQuery {
return criteriaBuilder.notEqual(
parseKey(root, x.key), c.convert(x.value, String.class));
});
map.put(
Tuple.of("neq", Boolean.class),
(Root root, CriteriaBuilder criteriaBuilder, QueryItem x, ConversionService c) -> {
return criteriaBuilder.notEqual(
parseKey(root, x.key), c.convert(x.value, Boolean.class));
});
map.put(
Tuple.of("neq", Integer.class),

View File

@@ -0,0 +1,338 @@
package cn.lihongjie.coal.common;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Splitter;
import freemarker.core.Environment;
import freemarker.template.*;
import freemarker.template.utility.DeepUnwrap;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
@UtilityClass
public class FreeMakerUtils {
private static final Configuration cfg;
private static final Cache<String, Template> templateCache =
Caffeine.newBuilder().maximumSize(10_000).build();
static {
// Create your Configuration instance, and specify if up to what FreeMarker
// version (here 2.3.32) do you want to apply the fixes that are not 100%
// backward-compatible. See the Configuration JavaDoc for details.
cfg = new Configuration(Configuration.VERSION_2_3_32);
// Specify the source where the template files come from. Here I set a
// plain directory for it, but non-file-system sources are possible too:
// From here we will set the settings recommended for new projects. These
// aren't the defaults for backward compatibilty.
// Set the preferred charset template files are stored in. UTF-8 is
// a good choice in most applications:
cfg.setDefaultEncoding("UTF-8");
// Sets how errors will appear.
// During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is better.
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// Don't log exceptions inside FreeMarker that it will thrown at you anyway:
cfg.setLogTemplateExceptions(false);
// Wrap unchecked exceptions thrown during template processing into TemplateException-s:
cfg.setWrapUncheckedExceptions(true);
// Do not fall back to higher scopes when reading a null loop variable:
cfg.setFallbackOnNullLoopVariable(false);
// To accomodate to how JDBC returns values; see Javadoc!
cfg.setSQLDateAndTimeTimeZone(TimeZone.getDefault());
cfg.setNumberFormat("c");
cfg.setSharedVariable("if", new MyBatisIf());
cfg.setSharedVariable("trim", new MyBatisTrim());
cfg.setSharedVariable("where", new MyBatisTrim("where", "AND |OR "));
cfg.setSharedVariable("set", new MyBatisTrim("set", ","));
cfg.setSharedVariable("foreach", new MyBatisForeach());
}
private static String getString(Map params, String key) throws TemplateModelException {
return getString(params, key, true, null);
}
private static String getString(Map params, String key, String defaultVal)
throws TemplateModelException {
return getString(params, key, false, defaultVal);
}
private static String getString(Map params, String key, boolean required, String defaultVal)
throws TemplateModelException {
Object o = params.get(key);
if (o == null && required) {
throw new TemplateModelException(key + " is required");
}
if (o == null) {
return defaultVal;
}
if (!(o instanceof SimpleScalar s)) {
throw new TemplateModelException(key + " must be string");
}
return StringUtils.defaultIfEmpty(s.getAsString(), defaultVal);
}
private static Boolean getBoolean(Map params, String key, boolean required, Boolean defaultVal)
throws TemplateModelException {
Object o = params.get(key);
if (o == null && required) {
throw new TemplateModelException(key + " is required");
}
if (o == null) {
return defaultVal;
}
if ((o instanceof SimpleScalar s)) {
return Boolean.parseBoolean(s.getAsString());
} else if (o instanceof TemplateBooleanModel s) {
return s.getAsBoolean();
} else {
throw new TemplateModelException(key + " must be boolean");
}
}
@SneakyThrows
public static String render( String template, Object model) {
if (StringUtils.isBlank(template)) {
return "";
}
if (model == null) {
model = new HashMap<>();
}
String hex = DigestUtils.md5DigestAsHex(template.getBytes(StandardCharsets.UTF_8));
Template ft =
templateCache
.asMap()
.computeIfAbsent(
hex,
k -> {
try {
return new Template(hex, template, cfg);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
StringWriter out = new StringWriter();
ft.process(model, out);
return out.toString();
}
public static class MyBatisIf implements TemplateDirectiveModel {
@Override
public void execute(
Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
Object o = params.get("test");
if (o == null) {
throw new TemplateException("test is required", env);
}
if (o instanceof TemplateBooleanModel b) {
if (b.getAsBoolean() && body != null) {
body.render(env.getOut());
}
} else {
throw new TemplateException("test must be boolean", env);
}
}
}
/**
* <foreach item="item" index="index" collection="list" open="ID in (" separator="," close=")"
* nullable="true"> #{item} </foreach>
*/
public static class MyBatisForeach implements TemplateDirectiveModel {
@Override
public void execute(
Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
String item = getString(params, "item", "__item");
String index = getString(params, "index", "__index");
String open = getString(params, "open", "");
String separator = getString(params, "separator", "");
String close = getString(params, "close", "");
Boolean nullable = getBoolean(params, "nullable", false, true);
Object collection = params.get("collection");
if (nullable && collection == null) {
return;
}
if (collection == null) {
throw new TemplateException("collection is null and nullable set to false", env);
}
Object object = DeepUnwrap.permissiveUnwrap((TemplateModel) collection);
if (object instanceof String){
object =
DeepUnwrap.permissiveUnwrap(
env.getDataModelOrSharedVariable((String) object));
}else if (object instanceof TemplateModelAdapter adapter){
object = DeepUnwrap.unwrap(adapter.getTemplateModel());
}
env.getOut().write(open);
if (object instanceof Iterable<?> iterable) {
int idx = 0;
for (Object o : iterable) {
env.setVariable(item, env.getObjectWrapper().wrap(o));
env.setVariable(index, env.getObjectWrapper().wrap(idx));
idx++;
body.render(env.getOut());
env.getOut().write(separator);
}
} else if (object instanceof Map<?, ?> map) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
env.setVariable(item, env.getObjectWrapper().wrap(entry.getValue()));
env.setVariable(index, env.getObjectWrapper().wrap(entry.getKey()));
body.render(env.getOut());
env.getOut().write(separator);
}
} else if (object.getClass().isArray()) {
Object[] array = (Object[]) object;
for (int i = 0; i < array.length; i++) {
env.setVariable(item, env.getObjectWrapper().wrap(array[i]));
env.setVariable(index, env.getObjectWrapper().wrap(i));
body.render(env.getOut());
env.getOut().write(separator);
}
} else {
throw new TemplateException("collection must be iterable/map/array", env);
}
env.getOut().write(close);
}
}
public static class MyBatisTrim implements TemplateDirectiveModel {
private SimpleScalar defaultPrefix;
private SimpleScalar defaultPrefixOverrides;
public MyBatisTrim() {}
public MyBatisTrim(String defaultPrefix, String defaultPrefixOverrides) {
Assert.hasLength(defaultPrefix, "defaultPrefix is required");
Assert.hasLength(defaultPrefixOverrides, "defaultPrefixOverrides is required");
this.defaultPrefix = new SimpleScalar(defaultPrefix);
this.defaultPrefixOverrides = new SimpleScalar(defaultPrefixOverrides);
}
@Override
public void execute(
Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
Object p = ObjectUtils.defaultIfNull(params.get("prefix"), defaultPrefix);
Object po =
ObjectUtils.defaultIfNull(
params.get("prefixOverrides"), defaultPrefixOverrides);
try {
Assert.isInstanceOf(SimpleScalar.class, p, "prefix must be string");
Assert.isInstanceOf(SimpleScalar.class, po, "prefixOverrides must be string");
String prefix = ((SimpleScalar) p).getAsString();
String prefixOverrides = ((SimpleScalar) po).getAsString();
if (body != null) {
StringWriter out = new StringWriter();
body.render(out);
String bodyStr = out.toString();
if (StringUtils.isNotBlank(bodyStr)) {
Iterable<String> iterable =
Splitter.on("|").omitEmptyStrings().split(prefixOverrides);
for (String s : iterable) {
bodyStr =
RegExUtils.replaceFirst(
bodyStr,
Pattern.compile(
"^(\\s*)" + Pattern.quote(s), CASE_INSENSITIVE),
"$1");
}
}
bodyStr = prefix + " " + bodyStr;
env.getOut().write(bodyStr);
}
} catch (Exception e) {
throw new TemplateException(e.getMessage(), e, env);
}
}
}
}

View File

@@ -1,19 +1,33 @@
package cn.lihongjie.coal.common;
import static org.springframework.data.jpa.repository.query.QueryUtils.toOrders;
import com.google.common.base.CaseFormat;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import jakarta.persistence.*;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.support.PageableUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -26,11 +40,15 @@ public class JpaUtils {
(String name) -> CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
public static List<Map> convertTuplesToRawMap(List<Tuple> tuples) {
return convertTuplesToMap(tuples, Function.identity(), Function.identity()).stream().map(x -> (Map) x).collect(Collectors.toList());
return convertTuplesToMap(tuples, Function.identity(), Function.identity()).stream()
.map(x -> (Map) x)
.collect(Collectors.toList());
}
public static List<Map<String, Object>> convertTuplesToMap(List<Tuple> tuples) {
return convertTuplesToMap(tuples, Function.identity(), Function.identity());
}
public static Function<Object, Object> handleArray =
(Object value) -> {
if (value == null) {
@@ -51,14 +69,14 @@ public class JpaUtils {
};
public static <T> void mergeMapToPojo(
Iterable<T> pojos,
Iterable<Map> maps,
ConversionService conversionService) {
mergeMapToPojo(pojos, x -> ((Function<Object, String>) ReflectUtils::getId).apply(x), maps, "id", conversionService);
Iterable<T> pojos, Iterable<Map> maps, ConversionService conversionService) {
mergeMapToPojo(
pojos,
x -> ((Function<Object, String>) ReflectUtils::getId).apply(x),
maps,
"id",
conversionService);
}
@SneakyThrows
@@ -93,12 +111,11 @@ public class JpaUtils {
continue;
}
Map map = mapMap.get(id);
for (Object key : map.keySet()) {
if (StringUtils.equals(key.toString(), mapKey)){
if (StringUtils.equals(key.toString(), mapKey)) {
continue;
}
@@ -121,13 +138,154 @@ public class JpaUtils {
Field field = ReflectUtils.getField(pojo.getClass(), fieldName).get();
ReflectUtils.writeField(pojo, fieldName, conversionService.convert(value, field.getType()));
ReflectUtils.writeField(
pojo, fieldName, conversionService.convert(value, field.getType()));
}
}
}
public static <T> List<T> execNativeQuery(
EntityManager entityManager, String sql, Object parameter, Class<T> clazz) {
if (clazz.isAssignableFrom(Map.class)) {
Query query = entityManager.createNativeQuery(sql, Tuple.class);
setQueryParameter(query, parameter);
return convertTuplesToMap(query.getResultList(), underscoreToCamelCase);
} else {
Query query = entityManager.createNativeQuery(sql, clazz);
setQueryParameter(query, parameter);
return query.getResultList();
}
}
public static void setQueryParameter(Query query, Object parameter) {
if (query == null || parameter == null) {
return;
}
if (parameter instanceof Map) {
Map<String, Object> map = (Map<String, Object>) parameter;
for (String key : map.keySet()) {
query.setParameter(key, map.get(key));
}
} else if (parameter instanceof Iterable<?> it) {
query.setParameter("param", it);
int index = 0;
for (Object o : it) {
query.setParameter("param" + index, o);
}
} else {
ReflectUtils.getAllFieldsList(parameter.getClass()).stream()
.filter(x -> !Modifier.isStatic(x.getModifiers()))
.forEach(
x -> {
Object value = ReflectUtils.getFieldValue(parameter, x.getName());
query.setParameter(x.getName(), value);
});
}
}
public static <S extends T> TypedQuery<S> getQuery(
EntityManager entityManager, Specification<S> spec, Class<S> domainClass, Sort sort) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<S> query = builder.createQuery(domainClass);
Root<S> root = applySpecificationToCriteria(entityManager, spec, domainClass, query);
query.select(root);
if (sort.isSorted()) {
query.orderBy(toOrders(sort, root, builder));
}
return (entityManager.createQuery(query));
}
public static <S extends T> Page<S> readPage(
EntityManager entityManager,
TypedQuery<S> query,
final Class<S> domainClass,
Pageable pageable,
@Nullable Specification<S> spec) {
if (pageable.isPaged()) {
query.setFirstResult(PageableUtils.getOffsetAsInteger(pageable));
query.setMaxResults(pageable.getPageSize());
}
return PageableExecutionUtils.getPage(
query.getResultList(),
pageable,
() -> executeCountQuery(getCountQuery(entityManager, spec, domainClass)));
}
private static long executeCountQuery(TypedQuery<Long> query) {
Assert.notNull(query, "TypedQuery must not be null");
List<Long> totals = query.getResultList();
long total = 0L;
for (Long element : totals) {
total += element == null ? 0 : element;
}
return total;
}
public static <S extends T> TypedQuery<Long> getCountQuery(
EntityManager entityManager, @Nullable Specification<S> spec, Class<S> domainClass) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<S> root = applySpecificationToCriteria(entityManager, spec, domainClass, query);
if (query.isDistinct()) {
query.select(builder.countDistinct(root));
} else {
query.select(builder.count(root));
}
// Remove all Orders the Specifications might have applied
query.orderBy(Collections.emptyList());
return (entityManager.createQuery(query));
}
private static <S, U extends T> Root<U> applySpecificationToCriteria(
EntityManager entityManager,
@Nullable Specification<U> spec,
Class<U> domainClass,
CriteriaQuery<S> query) {
Assert.notNull(domainClass, "Domain class must not be null");
Assert.notNull(query, "CriteriaQuery must not be null");
Root<U> root = query.from(domainClass);
if (spec == null) {
return root;
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
return root;
}
public static List<Map<String, Object>> convertTuplesToMap(
List<Tuple> tuples, Function<String, String> keyMapper) {
return convertTuplesToMap(tuples, keyMapper, Function.identity());

View File

@@ -16,12 +16,14 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@UtilityClass
public class ReflectUtils {
@@ -94,12 +96,23 @@ public class ReflectUtils {
return getFieldCache.get(
new Tuple2<>(cls, fieldName),
() -> {
return fieldCache.get(cls, () -> FieldUtils.getAllFieldsList(cls)).stream()
return doGetAllFields(cls).stream()
.filter(x -> x.getName().equals(fieldName))
.findFirst();
});
}
private static @NotNull List<Field> doGetAllFields(Class cls) throws ExecutionException {
return fieldCache.get(cls, () -> FieldUtils.getAllFieldsList(cls));
}
@SneakyThrows
public static List<Field> getAllFieldsList(Class cls){
return doGetAllFields(cls);
}
@SneakyThrows
public static Object invokeMethod(Object object, String methodName) {
return MethodUtils.invokeMethod(object, methodName);

View File

@@ -462,6 +462,11 @@ public class WeightDeviceDataEntity extends OrgCommonEntity {
private LocalDateTime minTime;
private Boolean invalid = false;
private Boolean finished = true;
@Override
public void prePersist() {
super.prePersist();
@@ -481,5 +486,9 @@ public class WeightDeviceDataEntity extends OrgCommonEntity {
.filter(Objects::nonNull)
.min(LocalDateTime::compareTo)
.orElse(null);
this.finished = this.pzTime!=null && this.mzTime!=null && this.ecgbTime!=null && this.ycgbTIme!=null;
}
}

View File

@@ -188,6 +188,11 @@ public class WeightDeviceDataService
where += " and d.specification = :specification ";
}
where += " and ( d.invalid is null or !d.invalid ) ";
where += " and ( d.finished is null or d.finished ) ";
var sql =
"select DATE_TRUNC('"
+ request.getTimeDimension()
@@ -259,6 +264,8 @@ public class WeightDeviceDataService
countQuery.setParameter("specification", "%" + request.getSpecification() + "%");
}
var resultList = JpaUtils.convertTuplesToMap(selectQuery.getResultList());
var ans =

View File

@@ -0,0 +1,9 @@
[#ftl]
[#-- @implicitly included --]
[#macro foreach item index collection open separator close nullable][/#macro]
[#macro where][/#macro]
[#macro set][/#macro]
[#macro trim prefix prefixOverrides][/#macro]
[#macro if test][/#macro]

View File

@@ -0,0 +1,165 @@
package cn.lihongjie.coal.common;
import static org.junit.jupiter.api.Assertions.*;
import freemarker.template.TemplateException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.*;
class FreeMakerUtilsTest {
@Test
void testIf() {
Assertions.assertEquals(
"t",
FreeMakerUtils.render(
"""
<@if test=b>
t
</@if>
""",
Map.of("b", true))
.trim());
Assertions.assertThrows(
TemplateException.class,
() -> {
FreeMakerUtils.render(
"""
<@if test=b>
t
</@if>
""",
Map.of("b", "1"))
.trim();
});
}
@Test
void testTrim() {
Assertions.assertEquals(
"where 1=1",
FreeMakerUtils.render(
"""
<@trim prefix="where" prefixOverrides="AND |OR ">
and 1=1
</@trim>
""",
Map.of("b", true))
.trim());
}
@Test
void testWhere() {
Assertions.assertEquals(
"where 1=1",
FreeMakerUtils.render(
"""
<@where >
and 1=1
</@where>
""",
Map.of("b", true))
.trim());
}
@Test
void testSet() {
Assertions.assertEquals(
"set a=1",
FreeMakerUtils.render(
"""
<@set >
,a=1
</@set>
""",
Map.of("b", true))
.trim());
}
@Test
void testObject() {
Assertions.assertEquals(
"set a=1",
FreeMakerUtils.render(
"""
<@set >
,a=1
</@set>
""",
new Demo("1") )
.trim());
}
@Test
void testForeach() {
Assertions.assertEquals(
"""
where id in ( 1
, 2
, 3
,)
""".trim(),
FreeMakerUtils.render(
"""
<@where>
<@foreach item="item" index="index" collection="ids"
open="id in (" separator="," close=")" nullable="true">
${item}
</@foreach>
</@where>
""",
Map.of("ids", List.of(1, 2, 3)))
.trim());
}
@Test
void testForeach2() {
Assertions.assertEquals(
"""
where id in ( :item_0
, :item_1
, :item_2
,)
"""
.trim(),
FreeMakerUtils.render(
"""
<@where>
<@foreach item="item" index="index" collection="ids"
open="id in (" separator="," close=")" nullable="true">
:item_${index}
</@foreach>
</@where>
""",
Map.of("ids", List.of(1, 2, 3)))
.trim());
}
record Demo(String k){
}
}