【企业级项目实操指南3】结合已有代码和MPJ实现统一的数据统计接口(3)
【企业级项目实操指南2】结合已有代码和MPJ实现统一的数据统计接口(1)
https://www.cnblogs.com/zwj/p/18841146/bnp-doit-001
【企业级项目实操指南2】结合已有代码和MPJ实现统一的数据统计接口(2)
https://www.cnblogs.com/zwj/p/18841146/bnp-doit-002
在1、2的基础上做一些补充,主要是应对 某个字段有自己的统计条件限制 问题
比如有一个金额列,但是我要把支出金额和收入金额分开统计,而不是所有金额单纯的做一个累加。
于是
@Data
public class AggregationColumn {
/**
* 字段名和统计方式 是前端提供的
* 如果有需要特殊处理的字段,可以在后端针对性补充 条件表达式condition 和自定义别名后缀,可参考 com/bytz/modules/erp/service/ContractService.java
*/
private String columnName;
private String condition; // 条件表达式,如 "字段 = '内容'"
private String aliasSuffix; // 自定义别名后缀
private AggregationFunction func; // 统计方式
}
工具类
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.jeecg.common.system.query.MPJQueryGenerator;
import org.jeecg.common.system.query.ReferenceColumn;
import java.lang.reflect.Field;
import java.util.List;
public class MPJAggregateUtil {
/**
* 构建聚合查询条件
*
* @param mpjLW 查询包装器
* @param entityClass 实体类
* @param columns 统计参数列表
* @param <T> 泛型实体类型
*/
public static <T> void buildAggregation(MPJLambdaWrapper<T> mpjLW, Class<?> entityClass, List<AggregationColumn> columns) {
// 遍历统计列配置
columns.forEach(column -> {
if (column == null) {
throw new IllegalArgumentException("请输入要统计的列");
}
String columnName = column.getColumnName();
AggregationFunction aggregationFunction = column.getFunc();
if (columnName == null || aggregationFunction == null) {
throw new IllegalArgumentException("请输入要统计的方式");
}
Field field = getFieldFromHierarchy(entityClass, columnName);
String sql = "";
if (field != null && field.isAnnotationPresent(ReferenceColumn.class)) {
ReferenceColumn annotation = field.getAnnotation(ReferenceColumn.class);
String tColumn = MPJQueryGenerator.buildReferenceColumn(annotation, mpjLW);
sql = buildSql(aggregationFunction, tColumn, field.getName(),
column.getCondition()!=null, column.getCondition(), column.getAliasSuffix());
} else {
String snakeCaseColumnName = camelToSnakeCase(columnName);
sql = buildSql(aggregationFunction, "t." + snakeCaseColumnName, snakeCaseColumnName,
column.getCondition()!=null, column.getCondition(), column.getAliasSuffix());
}
mpjLW.select(sql);
});
}
/**
* 构建 SQL 聚合表达式
*
* @param aggregationFunction 聚合函数,如 SUM, COUNT, AVG 等
* @param columnExpression 列表达式,通常是列名或带有表前缀的列名(例如 "t.money")
* @param aliasPrefix 别名前缀,通常与列名或实体类中的字段名相关,用于生成结果集中的别名
* @param conditional 是否使用条件聚合。如果为 true,则会根据提供的条件构造 CASE WHEN 语句
* @param condition 条件表达式,当 conditional 为 true 时使用,定义了聚合计算时应满足的条件
* @param aliasSuffix 自定义别名后缀,用于区分不同聚合结果或指定特定的别名后缀(例如 income 或 expense)
* @return 完整的 SQL 表达式,包含聚合函数、可能的条件逻辑以及对应的别名
*/
private static String buildSql(AggregationFunction aggregationFunction,
String columnExpression,
String aliasPrefix,
boolean conditional,
String condition,
String aliasSuffix) {
if (conditional && condition != null && !condition.isEmpty()) {
return String.format("SUM(CASE WHEN %s THEN %s ELSE 0 END) AS %s__%s",
condition, columnExpression, aliasPrefix, aliasSuffix);
}
String aliasName = aliasSuffix != null ? aliasSuffix : aggregationFunction.name().toLowerCase();
switch (aggregationFunction) {
case COUNT_DISTINCT:
return String.format("COUNT(DISTINCT %s) AS %s__count", columnExpression, aliasPrefix);
case COUNT:
case SUM:
case AVG:
case MIN:
case MAX:
return String.format("%s(%s) AS %s__%s", aggregationFunction.getFunctionName(), columnExpression, aliasPrefix, aliasName);
default:
throw new IllegalArgumentException("Unsupported aggregation function: " + aggregationFunction);
}
}
/**
* 小驼峰转蛇形命名(snake_case)
*
* @param str 输入的小驼峰格式字符串
* @return 转换后的蛇形命名字符串(snake_case)
*/
public static String camelToSnakeCase(String str) {
if (str == null || str.isEmpty()) {
return str;
}
StringBuilder result = new StringBuilder();
for (char c : str.toCharArray()) {
if (Character.isUpperCase(c)) {
result.append("_").append(Character.toLowerCase(c));
} else {
result.append(c);
}
}
return result.toString();
}
/**
* 从类及其父类中递归查找字段
*
* @param clazz 类
* @param fieldName 字段名
* @return 找到的字段,如果未找到则返回 null
*/
private static Field getFieldFromHierarchy(Class<?> clazz, String fieldName) {
while (clazz != null && !clazz.equals(Object.class)) {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass(); // 递归查找父类
}
}
return null; // 如果未找到字段,返回 null
}
}
使用
public List<Map<String, Object>> statWithJoin(ContractForListRes entity, Map<String, String[]> queryParams, List<AggregationColumn> columns) {
MPJLambdaWrapper<Contract> mpjLW = new MPJLambdaWrapper<>(Contract.class)
.leftJoin(Project.class, Project::getId, Contract::getProjectId)
.leftJoin(Org.class, Org::getId, Contract::getTheirOrgId)
.leftJoin(SysUser.class, SysUser::getId, Contract::getCreateBy);
MPJQueryGenerator.installMPJ(mpjLW, entity, queryParams);
// 检查是否有需要特殊处理的字段
// "columnName": "amount"
// 使用 Stream API 查找 amount 字段并处理
boolean containsAmount = columns.stream()
.anyMatch(column -> "amount".equals(column.getColumnName()));
if (containsAmount) {
// 移除原始的 amount 列
columns.removeIf(column -> "amount".equals(column.getColumnName()));
// 添加需要特殊处理的列
AggregationColumn incomeColumn = new AggregationColumn();
incomeColumn.setColumnName("amount");
incomeColumn.setFunc(AggregationFunction.SUM);
incomeColumn.setCondition("contract_category = '1'");
incomeColumn.setAliasSuffix("sales"); // 销售
AggregationColumn expenseColumn = new AggregationColumn();
expenseColumn.setColumnName("amount");
expenseColumn.setFunc(AggregationFunction.SUM);
expenseColumn.setCondition("contract_category = '2'");
expenseColumn.setAliasSuffix("purchase"); // 采购
columns.add(incomeColumn);
columns.add(expenseColumn);
}
MPJAggregateUtil.buildAggregation(mpjLW, ContractForListRes.class ,columns);
return this.selectJoinMaps(mpjLW);
}
这种方式,就能够实现对某个字段增加专属它的查询条件(虽然有很大局限性)
那么,接下来就谈问题:
AggregationColumn作为框架,应该提供通用功能,并为特殊需求提供可能性,这就是所谓的OCP (Open Close Principle)。注意理解“可能性”,如果可能性存在局限,那就说明设计不够完善,或者需求还不清晰,宁可不做。而且在Service层中加入数据表字段本来就不是好的实践,相当于领域层就必须知道数据库(Adapter)才可以做查询。
再说回需求,用户给的条件是基础,某个列的条件是额外附加的。所以,强烈建议,可不可以把用户给的条件作为基础,再附加某个特定列的条件,而且确保用MPJ而不是直接写数据表字段。这里我可以接受多次请求,宁可请求多次,也不要留下坏味道,给以后留下问题。
所以
总体不建议在AggregationColumn中增加condition,因为目前的condition支持的条件并不完备,一旦添加了condition你就会无止境的去扩张它,最终造成不可控。
建议暂时先只在Service类中处理这种特殊的聚合。
版 权 声 明

浙公网安备 33010602011771号