移除多个服务类中重复的findAll方法实现,统一使用父类方法 优化QueryService的findOneByProperty实现,提高可读性 为EntityService添加aliasFor方法支持字段别名 修复ContractVerifyWindowController的消息处理逻辑 添加查看验证状态的功能菜单项
450 lines
16 KiB
Java
450 lines
16 KiB
Java
package com.ecep.contract;
|
|
|
|
import java.util.List;
|
|
|
|
import com.ecep.contract.constant.ParamConstant;
|
|
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
|
|
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.util.StringUtils;
|
|
|
|
import com.ecep.contract.ds.MyRepository;
|
|
import com.ecep.contract.model.IdentityEntity;
|
|
import com.ecep.contract.model.Voable;
|
|
import com.ecep.contract.util.SpecificationUtils;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
import jakarta.persistence.criteria.Path;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
/**
|
|
* 实体服务基类
|
|
* 提供基础的CRUD操作和查询方法
|
|
*
|
|
* @param <T> 实体类型
|
|
* @param <VO> VO类型
|
|
* @param <ID> 主键类型
|
|
*/
|
|
@Slf4j
|
|
public abstract class EntityService<T extends Voable<VO>, VO, ID> {
|
|
/**
|
|
* 获取实体数据访问层接口
|
|
* 子类必须实现此方法,提供具体的实体数据访问层实例
|
|
*
|
|
* @return 实体数据访问层接口
|
|
*/
|
|
protected abstract MyRepository<T, ID> getRepository();
|
|
|
|
public T getById(ID id) {
|
|
return getRepository().findById(id).orElse(null);
|
|
}
|
|
|
|
/**
|
|
* 创建新实体实例
|
|
* 设置默认值或必要的属性
|
|
* 实例是游离态的,未存储到数据库
|
|
*
|
|
* @return 新实体实例
|
|
*/
|
|
public abstract T createNewEntity();
|
|
|
|
/**
|
|
* 统计所有实体数量
|
|
*
|
|
* @return 实体数量
|
|
*/
|
|
public long count() {
|
|
return getRepository().count();
|
|
}
|
|
|
|
/**
|
|
* 根据查询规范统计实体数量
|
|
*
|
|
* @param spec 查询规范
|
|
* @return 符合规范的实体数量
|
|
*/
|
|
public long count(Specification<T> spec) {
|
|
return getRepository().count(spec);
|
|
}
|
|
|
|
/**
|
|
* 根据JSON参数节点统计实体数量
|
|
*
|
|
* @param paramsNode JSON参数节点
|
|
* @return 符合参数节点规范的实体数量
|
|
*/
|
|
public long count(JsonNode paramsNode) {
|
|
return count(applyJsonParameter(paramsNode));
|
|
}
|
|
|
|
/**
|
|
* 保存实体到数据库
|
|
*
|
|
* @param entity 要保存的实体
|
|
* @return 保存后的实体
|
|
*/
|
|
public T save(T entity) {
|
|
return getRepository().save(entity);
|
|
}
|
|
|
|
/**
|
|
* 删除实体
|
|
*
|
|
* @param entity 要删除的实体
|
|
*/
|
|
public void delete(T entity) {
|
|
getRepository().delete(entity);
|
|
}
|
|
|
|
@Deprecated
|
|
protected abstract Specification<T> buildParameterSpecification(JsonNode paramsNode);
|
|
|
|
/**
|
|
* 应用JSON参数节点到查询规范
|
|
*
|
|
* @param node JSON参数节点
|
|
* @return 应用参数节点后的查询规范
|
|
*/
|
|
protected Specification<T> applyJsonParameter(JsonNode node) {
|
|
Specification<T> spec = SpecificationUtils.applySearchText(node, this::getSearchSpecification);
|
|
JsonNode filterNode = node.get(ParamConstant.KEY_FILTER);
|
|
if (filterNode != null) {
|
|
Specification<T> childSpec = buildFilterCondition(filterNode);
|
|
if (childSpec != null) {
|
|
spec = SpecificationUtils.and(spec, childSpec);
|
|
}
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
/**
|
|
* 根据JSON参数节点查询所有实体
|
|
*
|
|
* @param paramsNode JSON参数节点
|
|
* @param pageable 分页信息
|
|
* @return 符合参数节点规范的实体分页结果
|
|
*/
|
|
public Page<VO> findAll(JsonNode paramsNode, Pageable pageable) {
|
|
return findAll(applyJsonParameter(paramsNode), pageable).map(T::toVo);
|
|
}
|
|
|
|
/**
|
|
* 根据查询规范查询所有实体
|
|
*
|
|
* @param spec 查询规范
|
|
* @param pageable 分页信息
|
|
* @return 符合规范的实体分页结果
|
|
*/
|
|
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
|
|
return getRepository().findAll(spec, pageable);
|
|
}
|
|
|
|
/**
|
|
* 根据查询规范查询所有实体
|
|
*
|
|
* @param spec 查询规范
|
|
* @param sort 排序信息
|
|
* @return 符合规范的实体列表
|
|
*/
|
|
public List<T> findAll(Specification<T> spec, Sort sort) {
|
|
return getRepository().findAll(spec, sort);
|
|
}
|
|
|
|
/**
|
|
* 根据搜索文本查询所有实体
|
|
*
|
|
* @param searchText 搜索文本
|
|
* @return 符合搜索文本规范的实体列表
|
|
*/
|
|
public List<T> search(String searchText) {
|
|
Specification<T> spec = getSearchSpecification(searchText);
|
|
return getRepository().findAll(spec, Pageable.ofSize(10)).getContent();
|
|
}
|
|
|
|
/**
|
|
* 根据搜索文本构建查询规范
|
|
*
|
|
* @param searchText 搜索文本
|
|
* @return 符合搜索文本规范的查询规范
|
|
*/
|
|
public Specification<T> getSearchSpecification(String searchText) {
|
|
if (!StringUtils.hasText(searchText)) {
|
|
return null;
|
|
}
|
|
return SpecificationUtils.andWith(searchText, this::buildSearchSpecification);
|
|
}
|
|
|
|
/**
|
|
* 构建搜索规范
|
|
*
|
|
* @param searchText 搜索文本,非空
|
|
* @return 符合搜索文本规范的查询规范
|
|
*/
|
|
protected abstract Specification<T> buildSearchSpecification(String searchText);
|
|
|
|
/**
|
|
* 构建过滤条件规范
|
|
*
|
|
* @param filterNode 过滤条件节点
|
|
* @return 过滤条件规范
|
|
*/
|
|
private Specification<T> buildFilterCondition(JsonNode filterNode) {
|
|
String operatorStr = filterNode.get(ParamConstant.KEY_OPERATOR).asText();
|
|
|
|
if (isAndOrOperator(operatorStr)) {
|
|
if (filterNode.has(ParamConstant.KEY_CONDITIONS)) {
|
|
JsonNode conditionsNode = filterNode.get(ParamConstant.KEY_CONDITIONS);
|
|
if (conditionsNode.isArray()) {
|
|
List<Specification<T>> specs = new java.util.ArrayList<>();
|
|
|
|
for (JsonNode conditionNode : conditionsNode) {
|
|
Specification<T> childSpec = buildFilterCondition(conditionNode);
|
|
if (childSpec != null) {
|
|
specs.add(childSpec);
|
|
}
|
|
}
|
|
if (!specs.isEmpty()) {
|
|
ParamConstant.Operator op = ParamConstant.Operator.AND;
|
|
if (ParamConstant.Operator.OR.name().equalsIgnoreCase(operatorStr)) {
|
|
op = ParamConstant.Operator.OR;
|
|
}
|
|
return (op == ParamConstant.Operator.AND) ? Specification.allOf(specs)
|
|
: Specification.anyOf(specs);
|
|
}
|
|
} else {
|
|
log.debug("filterNode 中没有 conditions 数组");
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String field = filterNode.get(ParamConstant.KEY_FIELD).asText();
|
|
if (!StringUtils.hasText(field)) {
|
|
log.debug("filterNode 中没有 field 字段");
|
|
return null;
|
|
}
|
|
|
|
field = aliasFor(field, filterNode);
|
|
|
|
if (ParamConstant.KEY_equal.equals(operatorStr)) {
|
|
return buildEqualSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_BETWEEN.equals(operatorStr)) {
|
|
return buildBetweenSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_like.equals(operatorStr)) {
|
|
return buildLikeSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_in.equals(operatorStr)) {
|
|
return buildInSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_notIn.equals(operatorStr)) {
|
|
return buildNotInSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_greaterThan.equals(operatorStr)) {
|
|
return buildGreaterThanSpecification(field, filterNode);
|
|
}
|
|
if (ParamConstant.KEY_lessThan.equals(operatorStr)) {
|
|
return buildLessThanSpecification(field, filterNode);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 为查询字段添加别名
|
|
*
|
|
* @param field
|
|
* @param filterNode
|
|
* @return
|
|
*/
|
|
protected String aliasFor(String field, JsonNode filterNode) {
|
|
return field;
|
|
}
|
|
|
|
private <X extends Comparable<? super X>> Specification<T> buildLessThanSpecification(String field,
|
|
JsonNode filterNode) {
|
|
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
|
if (valueNode == null || valueNode.isNull()) {
|
|
log.debug("lessThan 操作符需要 value 字段");
|
|
return null;
|
|
}
|
|
return (root, query, cb) -> {
|
|
// 支持嵌套属性路径,如 company.id
|
|
String[] fieldPath = field.split("\\.");
|
|
Path<?> path = root;
|
|
for (String segment : fieldPath) {
|
|
path = path.get(segment);
|
|
}
|
|
Class<X> clz = (Class<X>) path.getJavaType();
|
|
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
|
X value = objectMapper.convertValue(valueNode, clz);
|
|
return cb.lessThan(path.as(clz), value);
|
|
};
|
|
}
|
|
|
|
private Specification<T> buildGreaterThanSpecification(String field, JsonNode filterNode) {
|
|
// TODO 实现大于操作符的逻辑
|
|
throw new UnsupportedOperationException("Unimplemented method 'buildGreaterThanSpecification'");
|
|
}
|
|
|
|
private Specification<T> buildNotInSpecification(String field, JsonNode filterNode) {
|
|
// TODO 实现 NOT IN 操作符的逻辑
|
|
throw new UnsupportedOperationException("Unimplemented method 'buildNotInSpecification'");
|
|
}
|
|
|
|
private Specification<T> buildInSpecification(String field, JsonNode filterNode) {
|
|
// TODO 实现 IN 操作符的逻辑
|
|
throw new UnsupportedOperationException("Unimplemented method 'buildInSpecification'");
|
|
}
|
|
|
|
private Specification<T> buildLikeSpecification(String field, JsonNode filterNode) {
|
|
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
|
if (valueNode == null || !valueNode.isTextual()) {
|
|
log.debug("LIKE 操作符需要 value 为字符串");
|
|
return null;
|
|
}
|
|
|
|
ParamConstant.Mode mode = ParamConstant.Mode.CONTAINS;
|
|
if (filterNode.has(ParamConstant.KEY_MODE)) {
|
|
mode = ParamConstant.Mode.valueOf(filterNode.get(ParamConstant.KEY_MODE).asText());
|
|
}
|
|
|
|
String likeValue;
|
|
switch (mode) {
|
|
case STARTS_WITH:
|
|
likeValue = valueNode.asText() + "%";
|
|
break;
|
|
case ENDS_WITH:
|
|
likeValue = "%" + valueNode.asText();
|
|
break;
|
|
default:
|
|
likeValue = "%" + valueNode.asText() + "%";
|
|
break;
|
|
}
|
|
return (root, query, cb) -> {
|
|
// 支持 company.name 这种嵌套属性路径
|
|
String[] fieldPath = field.split("\\.");
|
|
Path<?> path = root;
|
|
for (String segment : fieldPath) {
|
|
path = path.get(segment);
|
|
}
|
|
return cb.like(path.as(String.class), likeValue);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 等于操作符的逻辑
|
|
*
|
|
* @param field
|
|
* @param filterNode
|
|
* @return
|
|
*/
|
|
private Specification<T> buildEqualSpecification(String field, JsonNode filterNode) {
|
|
// 等于操作符的逻辑
|
|
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
|
if (valueNode == null || valueNode.isNull()) {
|
|
return (root, query, cb) -> cb.isNull(root.get(field));
|
|
}
|
|
return (root, query, cb) -> {
|
|
// 支持 company.id 这种嵌套属性路径
|
|
String[] fieldPath = field.split("\\.");
|
|
Path<?> path = root;
|
|
for (String segment : fieldPath) {
|
|
path = path.get(segment);
|
|
}
|
|
Class<?> clz = path.getJavaType();
|
|
if (IdentityEntity.class.isAssignableFrom(clz)) {
|
|
if (valueNode.isNumber()) {
|
|
return cb.equal(path.get("id"), valueNode.asInt());
|
|
}
|
|
|
|
if (valueNode.isObject() && valueNode.has("id")) {
|
|
JsonNode identity = valueNode.get("id");
|
|
if (identity.isNumber()) {
|
|
return cb.equal(path.get("id"), identity.asInt());
|
|
}
|
|
}
|
|
}
|
|
if (clz == java.lang.Enum.class) {
|
|
// 将字符串转换为对应的枚举值
|
|
clz = ((SqmBasicValuedSimplePath) path).getExpressibleJavaType().getJavaTypeClass();
|
|
}
|
|
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
|
Object value = null;
|
|
try {
|
|
value = objectMapper.convertValue(valueNode, clz);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("field=" + field + ", clz=" + clz.getName() + ", value=" + valueNode, e);
|
|
}
|
|
// Object value = valueNode.isTextual() ? valueNode.asText()
|
|
// : valueNode.isNumber() ? valueNode.numberValue()
|
|
// : valueNode.isBoolean() ? valueNode.asBoolean() : valueNode;
|
|
return cb.equal(path, value);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* BETWEEN 操作符的逻辑
|
|
*
|
|
* @param field
|
|
* @param filterNode
|
|
* @return
|
|
*/
|
|
private <X extends Comparable<? super X>> Specification<T> buildBetweenSpecification(String field,
|
|
JsonNode filterNode) {
|
|
// BETWEEN 操作符:要求 value 为数组,且长度=2
|
|
JsonNode valueNode = filterNode.get(ParamConstant.KEY_VALUE);
|
|
JsonNode lowNode, highNode;
|
|
boolean includeBegin = false, includeEnd = false;
|
|
if (valueNode == null || valueNode.isNull()) {
|
|
throw new IllegalArgumentException(field + " 的 BETWEEN 操作符需要参数");
|
|
}
|
|
if (valueNode.isArray()) {
|
|
if (valueNode.size() != 2) {
|
|
throw new IllegalArgumentException(field + " 的 BETWEEN 操作符的 value 数组长度必须为 2");
|
|
}
|
|
lowNode = valueNode.get(0);
|
|
highNode = valueNode.get(1);
|
|
} else if (valueNode.isObject()) {
|
|
lowNode = valueNode.get(ParamConstant.KEY_between_begin);
|
|
highNode = valueNode.get(ParamConstant.KEY_between_end);
|
|
|
|
if (valueNode.has(ParamConstant.KEY_INCLUDE_BEGIN)) {
|
|
includeBegin = valueNode.get(ParamConstant.KEY_INCLUDE_BEGIN).asBoolean();
|
|
}
|
|
if (valueNode.has(ParamConstant.KEY_INCLUDE_END)) {
|
|
includeEnd = valueNode.get(ParamConstant.KEY_INCLUDE_END).asBoolean();
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException(field + " 的 BETWEEN 操作符的 value 必须为数组或对象");
|
|
}
|
|
|
|
if (lowNode == null || highNode == null || lowNode.isNull() || highNode.isNull()) {
|
|
throw new IllegalArgumentException(field + " 的 BETWEEN 操作符的 value 数组元素不能为空");
|
|
}
|
|
|
|
return (root, query, cb) -> {
|
|
// 支持嵌套属性路径,如 company.id
|
|
String[] fieldPath = field.split("\\.");
|
|
Path<?> path = root;
|
|
for (String segment : fieldPath) {
|
|
path = path.get(segment);
|
|
}
|
|
Class<X> clz = (Class<X>) path.getJavaType();
|
|
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
|
X lowVal = objectMapper.convertValue(lowNode, clz);
|
|
X highVal = objectMapper.convertValue(highNode, clz);
|
|
return cb.between(path.as(clz), lowVal, highVal);
|
|
};
|
|
}
|
|
|
|
private boolean isAndOrOperator(String str) {
|
|
return ParamConstant.Operator.AND.name().equalsIgnoreCase(str)
|
|
|| ParamConstant.Operator.OR.name().equalsIgnoreCase(str);
|
|
}
|
|
}
|